Gisle Aune
2 years ago
9 changed files with 319 additions and 24 deletions
-
3bus.go
-
43cmd/bustest/main.go
-
5commands/assign.go
-
4device/interfaces.go
-
6effects/manual.go
-
4interface.go
-
51internal/color/color.go
-
160services/effectenforcer.go
-
67services/scenemap.go
@ -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() |
||||
|
} |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue