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.

207 lines
4.3 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
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. "git.aiterp.net/lucifer3/server/internal/gentools"
  8. "github.com/google/uuid"
  9. "sync"
  10. "sync/atomic"
  11. "time"
  12. )
  13. func NewEffectEnforcer(resolver device.Resolver, sceneMap device.SceneMap) lucifer3.ActiveService {
  14. s := &effectEnforcer{
  15. resolver: resolver,
  16. sceneMap: sceneMap,
  17. list: make([]*effectEnforcerRun, 0, 16),
  18. index: make(map[string]*effectEnforcerRun, 8),
  19. }
  20. return s
  21. }
  22. type effectEnforcer struct {
  23. mu sync.Mutex
  24. resolver device.Resolver
  25. sceneMap device.SceneMap
  26. started uint32
  27. list []*effectEnforcerRun
  28. index map[string]*effectEnforcerRun
  29. }
  30. func (s *effectEnforcer) Active() bool {
  31. return true
  32. }
  33. func (s *effectEnforcer) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event) {
  34. switch event := event.(type) {
  35. case events.DeviceReady:
  36. // If the device is managed by the effect enforcer, cause the effect to be
  37. // re-ran.
  38. s.mu.Lock()
  39. if run, ok := s.index[event.ID]; ok {
  40. run.due = time.Now()
  41. }
  42. s.mu.Unlock()
  43. }
  44. }
  45. func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
  46. switch command := command.(type) {
  47. case commands.Assign:
  48. pointers := s.resolver.Resolve(command.Match)
  49. allowedIDs := make([]string, 0, len(pointers))
  50. for _, ptr := range pointers {
  51. if s.sceneMap.SceneID(ptr.ID) == nil {
  52. allowedIDs = append(allowedIDs, ptr.ID)
  53. }
  54. }
  55. if len(pointers) == 0 {
  56. if command.ID != nil {
  57. bus.RunEvent(events.AssignmentRemoved{ID: *command.ID})
  58. } else {
  59. bus.RunEvent(events.Log{
  60. Level: "info",
  61. Code: "assignment_matched_none",
  62. Message: "Assignment to \"" + command.Match + "\" matched no devices.",
  63. })
  64. }
  65. return
  66. }
  67. id := uuid.New()
  68. if command.ID != nil {
  69. id = *command.ID
  70. }
  71. bus.RunEvent(events.AssignmentCreated{
  72. ID: id,
  73. Match: command.Match,
  74. Effect: command.Effect,
  75. })
  76. s.mu.Lock()
  77. // Create a new run
  78. newRun := &effectEnforcerRun{
  79. id: id,
  80. due: time.Now(),
  81. ids: allowedIDs,
  82. effect: command.Effect,
  83. }
  84. s.list = append(s.list, newRun)
  85. // Switch over the indices.
  86. for _, id := range allowedIDs {
  87. if oldRun := s.index[id]; oldRun != nil {
  88. oldRun.remove(id)
  89. }
  90. s.index[id] = newRun
  91. }
  92. s.mu.Unlock()
  93. if atomic.CompareAndSwapUint32(&s.started, 0, 1) {
  94. go s.runLoop(bus)
  95. }
  96. for _, deviceID := range allowedIDs {
  97. bus.RunEvent(events.DeviceAssigned{
  98. DeviceID: deviceID,
  99. AssignmentID: gentools.Ptr(id),
  100. })
  101. }
  102. }
  103. }
  104. func (s *effectEnforcer) runLoop(bus *lucifer3.EventBus) {
  105. deleteList := make([]int, 0, 8)
  106. batch := make(commands.SetStateBatch, 64)
  107. deleteIDs := make([]uuid.UUID, 0)
  108. for now := range time.NewTicker(time.Millisecond * 50).C {
  109. deleteIDs = deleteIDs[:0]
  110. s.mu.Lock()
  111. for i, run := range s.list {
  112. if run.dead {
  113. deleteIDs = append(deleteIDs, run.id)
  114. deleteList = append(deleteList, i-len(deleteList))
  115. continue
  116. }
  117. if run.due.IsZero() || now.Before(run.due) {
  118. continue
  119. }
  120. for j, id := range run.ids {
  121. if id == "" {
  122. continue
  123. }
  124. state := run.effect.State(j, len(run.ids), run.round)
  125. batch[id] = state
  126. }
  127. if freq := run.effect.Frequency(); freq > 0 {
  128. if freq < 100*time.Millisecond {
  129. run.due = now
  130. } else {
  131. run.due = run.due.Add(freq)
  132. }
  133. run.round += 1
  134. } else {
  135. run.due = time.Time{}
  136. }
  137. }
  138. if len(deleteList) > 0 {
  139. for _, i := range deleteList {
  140. s.list = append(s.list[:i], s.list[i+1:]...)
  141. }
  142. deleteList = deleteList[:0]
  143. }
  144. s.mu.Unlock()
  145. for _, id := range deleteIDs {
  146. bus.RunEvent(events.AssignmentRemoved{ID: id})
  147. }
  148. if len(batch) > 0 {
  149. bus.RunCommand(batch)
  150. batch = make(commands.SetStateBatch, 64)
  151. }
  152. }
  153. }
  154. type effectEnforcerRun struct {
  155. id uuid.UUID
  156. due time.Time
  157. ids []string
  158. effect lucifer3.Effect
  159. dead bool
  160. round int
  161. }
  162. // remove takes out an id from the effect, and returns whether the effect is empty.
  163. func (r *effectEnforcerRun) remove(id string) {
  164. anyLeft := false
  165. for i, id2 := range r.ids {
  166. if id2 == id {
  167. r.ids[i] = ""
  168. } else if id2 != "" {
  169. anyLeft = true
  170. }
  171. }
  172. if !anyLeft {
  173. r.dead = true
  174. }
  175. return
  176. }