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