Files
artmap/remap/engine.go

135 lines
3.3 KiB
Go
Raw Normal View History

package remap
import (
"sync"
"github.com/gopatchy/artmap/artnet"
"github.com/gopatchy/artmap/config"
)
// Output represents a remapped DMX output
type Output struct {
Universe artnet.Universe
Protocol config.Protocol
Data [512]byte
}
// outputKey uniquely identifies an output destination
type outputKey struct {
Universe artnet.Universe
Protocol config.Protocol
}
// sourceKey uniquely identifies an input source
type sourceKey struct {
Universe artnet.Universe
Protocol config.Protocol
}
// Engine handles DMX channel remapping
type Engine struct {
mappings []config.NormalizedMapping
// Index mappings by source universe and protocol for faster lookup
bySource map[sourceKey][]config.NormalizedMapping
// Persistent state for each output universe (merged from all sources)
state map[outputKey]*[512]byte
stateMu sync.Mutex
}
// NewEngine creates a new remapping engine
func NewEngine(mappings []config.NormalizedMapping) *Engine {
bySource := make(map[sourceKey][]config.NormalizedMapping)
for _, m := range mappings {
key := sourceKey{Universe: m.FromUniverse, Protocol: m.FromProto}
bySource[key] = append(bySource[key], m)
}
// Initialize state for all output universes
state := make(map[outputKey]*[512]byte)
for _, m := range mappings {
key := outputKey{Universe: m.ToUniverse, Protocol: m.Protocol}
if _, ok := state[key]; !ok {
state[key] = &[512]byte{}
}
}
return &Engine{
mappings: mappings,
bySource: bySource,
state: state,
}
}
// Remap applies mappings to incoming DMX data and returns outputs
func (e *Engine) Remap(srcProto config.Protocol, srcUniverse artnet.Universe, srcData [512]byte) []Output {
key := sourceKey{Universe: srcUniverse, Protocol: srcProto}
mappings, ok := e.bySource[key]
if !ok {
return nil
}
e.stateMu.Lock()
defer e.stateMu.Unlock()
// Track which outputs are affected by this input
affected := make(map[outputKey]bool)
for _, m := range mappings {
outKey := outputKey{Universe: m.ToUniverse, Protocol: m.Protocol}
affected[outKey] = true
// Update state for this output
outState := e.state[outKey]
// Copy channels into persistent state
for i := 0; i < m.Count; i++ {
srcChan := m.FromChannel + i
dstChan := m.ToChannel + i
if srcChan < 512 && dstChan < 512 {
outState[dstChan] = srcData[srcChan]
}
}
}
// Return outputs for all affected universes
result := make([]Output, 0, len(affected))
for outKey := range affected {
result = append(result, Output{
Universe: outKey.Universe,
Protocol: outKey.Protocol,
Data: *e.state[outKey],
})
}
return result
}
// SourceUniverses returns all universes that have mappings
func (e *Engine) SourceUniverses() []artnet.Universe {
seen := make(map[artnet.Universe]bool)
for key := range e.bySource {
seen[key.Universe] = true
}
result := make([]artnet.Universe, 0, len(seen))
for u := range seen {
result = append(result, u)
}
return result
}
// DestUniverses returns all destination universes (for ArtNet discovery)
func (e *Engine) DestUniverses() []artnet.Universe {
seen := make(map[artnet.Universe]bool)
for _, m := range e.mappings {
if m.Protocol == config.ProtocolArtNet {
seen[m.ToUniverse] = true
}
}
result := make([]artnet.Universe, 0, len(seen))
for u := range seen {
result = append(result, u)
}
return result
}