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.
164 lines
3.3 KiB
164 lines
3.3 KiB
package services
|
|
|
|
import (
|
|
lucifer3 "git.aiterp.net/lucifer3/server"
|
|
"git.aiterp.net/lucifer3/server/commands"
|
|
"git.aiterp.net/lucifer3/server/device"
|
|
"git.aiterp.net/lucifer3/server/events"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
func NewEffectEnforcer(resolver device.Resolver, sceneMap device.SceneMap) lucifer3.ActiveService {
|
|
s := &effectEnforcer{
|
|
resolver: resolver,
|
|
sceneMap: sceneMap,
|
|
list: make([]*effectEnforcerRun, 0, 16),
|
|
index: make(map[string]*effectEnforcerRun, 8),
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
type effectEnforcer struct {
|
|
mu sync.Mutex
|
|
resolver device.Resolver
|
|
sceneMap device.SceneMap
|
|
|
|
started uint32
|
|
|
|
list []*effectEnforcerRun
|
|
index map[string]*effectEnforcerRun
|
|
}
|
|
|
|
func (s *effectEnforcer) Active() bool {
|
|
return true
|
|
}
|
|
|
|
func (s *effectEnforcer) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event) {
|
|
switch event := event.(type) {
|
|
case events.DeviceReady:
|
|
// If the device is managed by the effect enforcer, cause the effect to be
|
|
// re-ran.
|
|
s.mu.Lock()
|
|
if run, ok := s.index[event.ID]; ok && run.due.IsZero() {
|
|
run.due = time.Now()
|
|
}
|
|
s.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
|
|
switch command := command.(type) {
|
|
case commands.Assign:
|
|
pointers := s.resolver.Resolve(command.Match)
|
|
allowedIDs := make([]string, 0, len(pointers))
|
|
for _, ptr := range pointers {
|
|
if s.sceneMap.SceneID(ptr.ID) == nil {
|
|
allowedIDs = append(allowedIDs, ptr.ID)
|
|
}
|
|
}
|
|
|
|
s.mu.Lock()
|
|
// Create a new run
|
|
newRun := &effectEnforcerRun{
|
|
due: time.Now(),
|
|
ids: allowedIDs,
|
|
effect: command.Effect,
|
|
}
|
|
s.list = append(s.list, newRun)
|
|
|
|
// Switch over the indices.
|
|
for _, id := range allowedIDs {
|
|
if oldRun := s.index[id]; oldRun != nil {
|
|
oldRun.remove(id)
|
|
}
|
|
s.index[id] = newRun
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
if atomic.CompareAndSwapUint32(&s.started, 0, 1) {
|
|
go s.runLoop(bus)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *effectEnforcer) runLoop(bus *lucifer3.EventBus) {
|
|
deleteList := make([]int, 0, 8)
|
|
batch := make(commands.SetStateBatch, 64)
|
|
|
|
for now := range time.NewTicker(time.Millisecond * 50).C {
|
|
s.mu.Lock()
|
|
for i, run := range s.list {
|
|
if run.dead {
|
|
deleteList = append(deleteList, i-len(deleteList))
|
|
continue
|
|
}
|
|
if run.due.IsZero() || now.Before(run.due) {
|
|
continue
|
|
}
|
|
|
|
for j, id := range run.ids {
|
|
if id == "" {
|
|
continue
|
|
}
|
|
|
|
state := run.effect.State(j, len(run.ids), run.round)
|
|
batch[id] = state
|
|
}
|
|
|
|
if freq := run.effect.Frequency(); freq > 0 {
|
|
if freq < 100*time.Millisecond {
|
|
run.due = now
|
|
} else {
|
|
run.due = run.due.Add(freq)
|
|
}
|
|
|
|
run.round += 1
|
|
} else {
|
|
run.due = time.Time{}
|
|
}
|
|
}
|
|
|
|
if len(deleteList) > 0 {
|
|
for _, i := range deleteList {
|
|
deleteList = append(deleteList[:i], deleteList[i+1:]...)
|
|
}
|
|
|
|
deleteList = deleteList[:0]
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
if len(batch) > 0 {
|
|
bus.RunCommand(batch)
|
|
batch = make(commands.SetStateBatch, 64)
|
|
}
|
|
}
|
|
}
|
|
|
|
type effectEnforcerRun struct {
|
|
due time.Time
|
|
ids []string
|
|
effect lucifer3.Effect
|
|
dead bool
|
|
round int
|
|
}
|
|
|
|
// remove takes out an id from the effect, and returns whether the effect is empty.
|
|
func (r *effectEnforcerRun) remove(id string) {
|
|
anyLeft := false
|
|
for i, id2 := range r.ids {
|
|
if id2 == id {
|
|
r.ids[i] = ""
|
|
} else if id2 != "" {
|
|
anyLeft = true
|
|
}
|
|
}
|
|
|
|
if !anyLeft {
|
|
r.dead = true
|
|
}
|
|
|
|
return
|
|
}
|