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.

165 lines
3.3 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. "git.aiterp.net/lucifer3/server/events"
  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, event lucifer3.Event) {
  32. switch event := event.(type) {
  33. case events.DeviceReady:
  34. // If the device is managed by the effect enforcer, cause the effect to be
  35. // re-ran.
  36. s.mu.Lock()
  37. if run, ok := s.index[event.ID]; ok && run.due.IsZero() {
  38. run.due = time.Now()
  39. }
  40. s.mu.Unlock()
  41. }
  42. }
  43. func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
  44. switch command := command.(type) {
  45. case commands.Assign:
  46. pointers := s.resolver.Resolve(command.Match)
  47. allowedIDs := make([]string, 0, len(pointers))
  48. for _, ptr := range pointers {
  49. if s.sceneMap.SceneID(ptr.ID) == nil {
  50. allowedIDs = append(allowedIDs, ptr.ID)
  51. }
  52. }
  53. s.mu.Lock()
  54. // Create a new run
  55. newRun := &effectEnforcerRun{
  56. due: time.Now(),
  57. ids: allowedIDs,
  58. effect: command.Effect,
  59. }
  60. s.list = append(s.list, newRun)
  61. // Switch over the indices.
  62. for _, id := range allowedIDs {
  63. if oldRun := s.index[id]; oldRun != nil {
  64. oldRun.remove(id)
  65. }
  66. s.index[id] = newRun
  67. }
  68. s.mu.Unlock()
  69. if atomic.CompareAndSwapUint32(&s.started, 0, 1) {
  70. go s.runLoop(bus)
  71. }
  72. }
  73. }
  74. func (s *effectEnforcer) runLoop(bus *lucifer3.EventBus) {
  75. deleteList := make([]int, 0, 8)
  76. batch := make(commands.SetStateBatch, 64)
  77. for now := range time.NewTicker(time.Millisecond * 25).C {
  78. s.mu.Lock()
  79. for i, run := range s.list {
  80. if run.dead {
  81. deleteList = append(deleteList, i-len(deleteList))
  82. continue
  83. }
  84. if run.due.IsZero() || now.Before(run.due) {
  85. continue
  86. }
  87. for j, id := range run.ids {
  88. if id == "" {
  89. continue
  90. }
  91. state := run.effect.State(j, len(run.ids), run.round)
  92. batch[id] = state
  93. }
  94. if freq := run.effect.Frequency(); freq > 0 {
  95. if freq < 100*time.Millisecond {
  96. run.due = now
  97. } else {
  98. run.due = run.due.Add(freq)
  99. }
  100. run.round += 1
  101. } else {
  102. run.due = time.Time{}
  103. }
  104. }
  105. if len(deleteList) > 0 {
  106. for _, i := range deleteList {
  107. deleteList = append(deleteList[:i], deleteList[i+1:]...)
  108. }
  109. deleteList = deleteList[:0]
  110. }
  111. s.mu.Unlock()
  112. if len(batch) > 0 {
  113. bus.RunCommand(batch)
  114. batch = make(commands.SetStateBatch, 64)
  115. }
  116. }
  117. }
  118. type effectEnforcerRun struct {
  119. due time.Time
  120. ids []string
  121. effect lucifer3.Effect
  122. dead bool
  123. round int
  124. }
  125. // remove takes out an id from the effect, and returns whether the effect is empty.
  126. func (r *effectEnforcerRun) remove(id string) {
  127. anyLeft := false
  128. for i, id2 := range r.ids {
  129. if id2 == id {
  130. r.ids[i] = ""
  131. } else if id2 != "" {
  132. anyLeft = true
  133. }
  134. }
  135. if !anyLeft {
  136. r.dead = true
  137. }
  138. return
  139. }