You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
3.2 KiB

  1. package services
  2. import (
  3. lucifer3 "git.aiterp.net/lucifer3/server"
  4. "git.aiterp.net/lucifer3/server/commands"
  5. "git.aiterp.net/lucifer3/server/device"
  6. "math/rand"
  7. "sync"
  8. "sync/atomic"
  9. "time"
  10. )
  11. func NewEffectEnforcer(resolver device.Resolver, sceneMap device.SceneMap) lucifer3.ActiveService {
  12. s := &effectEnforcer{
  13. resolver: resolver,
  14. sceneMap: sceneMap,
  15. list: make([]*effectEnforcerRun, 0, 16),
  16. index: make(map[string]*effectEnforcerRun, 8),
  17. }
  18. return s
  19. }
  20. type effectEnforcer struct {
  21. mu sync.Mutex
  22. resolver device.Resolver
  23. sceneMap device.SceneMap
  24. started uint32
  25. list []*effectEnforcerRun
  26. index map[string]*effectEnforcerRun
  27. }
  28. func (s *effectEnforcer) Active() bool {
  29. return true
  30. }
  31. func (s *effectEnforcer) HandleEvent(_ *lucifer3.EventBus, _ lucifer3.Event) {}
  32. func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
  33. switch command := command.(type) {
  34. case commands.Assign:
  35. pointers := s.resolver.Resolve(command.Match)
  36. allowedIDs := make([]string, 0, len(pointers))
  37. for _, ptr := range pointers {
  38. if s.sceneMap.SceneID(ptr.ID) == nil {
  39. allowedIDs = append(allowedIDs, ptr.ID)
  40. }
  41. }
  42. s.mu.Lock()
  43. // Create a new run
  44. newRun := &effectEnforcerRun{
  45. due: time.Now(),
  46. ids: allowedIDs,
  47. effect: command.Effect,
  48. }
  49. s.list = append(s.list, newRun)
  50. // Remove the ids from any old run.
  51. for _, id := range allowedIDs {
  52. if oldRun := s.index[id]; oldRun != nil {
  53. oldRun.remove(id)
  54. }
  55. s.index[id] = newRun
  56. }
  57. s.mu.Unlock()
  58. if atomic.CompareAndSwapUint32(&s.started, 0, 1) {
  59. go s.runLoop(bus)
  60. }
  61. }
  62. }
  63. func (s *effectEnforcer) runLoop(bus *lucifer3.EventBus) {
  64. deleteList := make([]int, 0, 8)
  65. commandQueue := make([]commands.SetState, 0, 32)
  66. for now := range time.NewTicker(time.Millisecond * 100).C {
  67. s.mu.Lock()
  68. for i, run := range s.list {
  69. if run.dead {
  70. deleteList = append(deleteList, i-len(deleteList))
  71. continue
  72. }
  73. if run.due.IsZero() || now.Before(run.due) {
  74. continue
  75. }
  76. r := rand.Int()
  77. for j, id := range run.ids {
  78. if id == "" {
  79. continue
  80. }
  81. state := run.effect.State(j, len(run.ids), run.round, r)
  82. commandQueue = append(commandQueue, commands.SetState{
  83. ID: id,
  84. State: state,
  85. })
  86. }
  87. if freq := run.effect.Frequency(); freq > 0 {
  88. if freq < 100*time.Millisecond {
  89. run.due = now
  90. } else {
  91. run.due = run.due.Add(freq)
  92. }
  93. run.round += 1
  94. } else {
  95. run.due = time.Time{}
  96. }
  97. }
  98. if len(deleteList) > 0 {
  99. for _, i := range deleteList {
  100. deleteList = append(deleteList[:i], deleteList[i+1:]...)
  101. }
  102. deleteList = deleteList[:0]
  103. }
  104. s.mu.Unlock()
  105. if len(commandQueue) > 0 {
  106. for _, command := range commandQueue {
  107. bus.RunCommand(command)
  108. }
  109. commandQueue = commandQueue[:0]
  110. }
  111. }
  112. }
  113. type effectEnforcerRun struct {
  114. due time.Time
  115. ids []string
  116. effect lucifer3.Effect
  117. dead bool
  118. round int
  119. }
  120. // remove takes out an id from the effect, and returns whether the effect is empty.
  121. func (r *effectEnforcerRun) remove(id string) {
  122. anyLeft := false
  123. for i, id2 := range r.ids {
  124. if id2 == id {
  125. r.ids[i] = ""
  126. } else if id2 != "" {
  127. anyLeft = true
  128. }
  129. }
  130. if !anyLeft {
  131. r.dead = true
  132. }
  133. return
  134. }