scenemap and effect enforcers.

This commit is contained in:
2022-08-18 22:46:38 +02:00
parent d6350ff0c6
commit fd898050bf
9 changed files with 319 additions and 24 deletions
+160
View File
@@ -0,0 +1,160 @@
package services
import (
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/device"
"math/rand"
"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, _ lucifer3.Event) {}
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)
// Remove the ids from any old run.
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)
commandQueue := make([]commands.SetState, 0, 32)
for now := range time.NewTicker(time.Millisecond * 100).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
}
r := rand.Int()
for j, id := range run.ids {
if id == "" {
continue
}
state := run.effect.State(j, run.round, r)
commandQueue = append(commandQueue, commands.SetState{
ID: id,
State: 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(commandQueue) > 0 {
for _, command := range commandQueue {
bus.RunCommand(command)
}
commandQueue = commandQueue[:0]
}
}
}
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
}
+67
View File
@@ -0,0 +1,67 @@
package services
import (
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/device"
"log"
"sync"
)
func NewSceneMap(resolver device.Resolver) *SceneMap {
return &SceneMap{
resolver: resolver,
sceneMap: make(map[string]int64, 64),
}
}
type SceneMap struct {
resolver device.Resolver
mu sync.Mutex
sceneMap map[string]int64
}
func (s *SceneMap) SceneID(id string) *int64 {
s.mu.Lock()
defer s.mu.Unlock()
sceneID, ok := s.sceneMap[id]
if !ok {
return nil
}
return &sceneID
}
func (s *SceneMap) Active() bool {
return true
}
func (s *SceneMap) HandleEvent(*lucifer3.EventBus, lucifer3.Event) {}
func (s *SceneMap) HandleCommand(_ *lucifer3.EventBus, command lucifer3.Command) {
switch command := command.(type) {
case commands.ReplaceScene:
matched := s.resolver.Resolve(command.Match)
if len(matched) > 0 {
s.mu.Lock()
for _, ptr := range matched {
s.sceneMap[ptr.ID] = command.SceneID
}
s.mu.Unlock()
}
case commands.ClearScene:
matched := s.resolver.Resolve(command.Match)
if len(matched) > 0 {
s.mu.Lock()
for _, ptr := range matched {
log.Println("Got")
delete(s.sceneMap, ptr.ID)
}
s.mu.Unlock()
}
}
}