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.

153 lines
3.0 KiB

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