scenemap and effect enforcers.
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user