diff --git a/cmd/bustest/main.go b/cmd/bustest/main.go index df9e57e..ebf96ef 100644 --- a/cmd/bustest/main.go +++ b/cmd/bustest/main.go @@ -21,10 +21,11 @@ func main() { time.Sleep(time.Second / 2) + numbers := []int{5, 2, 3, 1, 4} for i, id := range []string{"e28c", "67db", "f744", "d057", "73c1"} { bus.RunEvent(events.HardwareState{ ID: "nanoleaf:10.80.1.11:" + id, - InternalName: fmt.Sprintf("Hexagon %d", i+1), + InternalName: fmt.Sprintf("Hexagon %d", numbers[i]), SupportFlags: device.SFlagPower | device.SFlagColor | device.SFlagIntensity, ColorFlags: device.CFlagRGB, State: device.State{}, @@ -45,13 +46,6 @@ func main() { time.Sleep(time.Second / 8) - bus.RunCommand(commands.ReplaceScene{ - Match: "lucifer:name:Hex*", - SceneID: 7, - }) - - time.Sleep(time.Second / 8) - bus.RunEvent(events.HardwareState{ ID: "nanoleaf:10.80.1.11:40e5", InternalName: "Hexagon 6", @@ -62,6 +56,13 @@ func main() { time.Sleep(time.Second / 8) + bus.RunCommand(commands.ReplaceScene{ + Match: "lucifer:name:Hex*", + SceneID: 7, + }) + + time.Sleep(time.Second / 8) + log.Println("Search \"**:Hexagon {1,5,6}\"") for _, dev := range resolver.Resolve("lucifer:name:Hexagon {1,5,6}") { log.Println("- ID:", dev.ID) diff --git a/services/resolver.go b/services/resolver.go index d35c4a7..b5b35c9 100644 --- a/services/resolver.go +++ b/services/resolver.go @@ -6,39 +6,29 @@ import ( "git.aiterp.net/lucifer3/server/device" "git.aiterp.net/lucifer3/server/events" "github.com/gobwas/glob" + "sort" + "strings" "sync" ) func NewResolver() *Resolver { return &Resolver{ - pointers: make([]*device.Pointer, 0, 128), + pointers: make([]*device.Pointer, 0, 128), + patternCache: make(map[string]glob.Glob), } } // Resolver implements both device.Resolver and lucifer3.Service. There should be only one of them as it keeps its // state from event-sourcery. type Resolver struct { - mu sync.Mutex - pointers []*device.Pointer -} - -func (r *Resolver) ensure(id string) *device.Pointer { - for _, ptr := range r.pointers { - if ptr.ID == id { - return ptr - } - } - - r.pointers = append(r.pointers, &device.Pointer{ - ID: id, - }) - - return r.pointers[len(r.pointers)-1] + mu sync.Mutex + pointers []*device.Pointer + patternCache map[string]glob.Glob } func (r *Resolver) resolve(pattern string) []*device.Pointer { - g, err := glob.Compile(pattern, ':') - if err != nil { + g := r.patternToGlob(pattern) + if g == nil { return []*device.Pointer{} } @@ -53,14 +43,14 @@ func (r *Resolver) resolve(pattern string) []*device.Pointer { } func (r *Resolver) Resolve(pattern string) []device.Pointer { - g, err := glob.Compile(pattern, ':') - if err != nil { - return []device.Pointer{} - } - r.mu.Lock() defer r.mu.Unlock() + g := r.patternToGlob(pattern) + if g == nil { + return []device.Pointer{} + } + res := make([]device.Pointer, 0, 32) for _, ptr := range r.pointers { if ptr.Matches(g) { @@ -83,6 +73,7 @@ func (r *Resolver) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event) { ptr := r.ensure(event.ID) if event.InternalName != "" && ptr.Name() == "" { ptr.AddAlias("lucifer:name:" + event.InternalName) + r.sortPointers() } r.mu.Unlock() } @@ -95,13 +86,81 @@ func (r *Resolver) HandleCommand(_ *lucifer3.EventBus, command lucifer3.Command) for _, ptr := range r.resolve(command.Match) { ptr.AddAlias(command.Alias) } + if strings.HasPrefix(command.Alias, "lucifer:name:") { + r.sortPointers() + } r.mu.Unlock() case commands.RemoveAlias: r.mu.Lock() for _, ptr := range r.resolve(command.Match) { - ptr.AddAlias(command.Alias) + ptr.RemoveAlias(command.Alias) + } + if strings.HasPrefix(command.Alias, "lucifer:name:") { + r.sortPointers() } r.mu.Unlock() } } + +func (r *Resolver) ensure(id string) *device.Pointer { + for _, ptr := range r.pointers { + if ptr.ID == id { + return ptr + } + } + + r.pointers = append(r.pointers, &device.Pointer{ + ID: id, + }) + + return r.pointers[len(r.pointers)-1] +} + +func (r *Resolver) patternToGlob(pattern string) glob.Glob { + if cached, ok := r.patternCache[pattern]; ok { + return cached + } + + g, err := glob.Compile(pattern, ':') + if err != nil { + return nil + } + + // Keep this in bounds. Random cache deletions after 128 length. + if len(r.patternCache) > 128 { + i := 64 + for k := range r.patternCache { + if i == 0 { + break + } + + delete(r.patternCache, k) + i -= 1 + } + } + + // Cache it + r.patternCache[pattern] = g + + return g +} + +func (r *Resolver) sortPointers() { + sort.Slice(r.pointers, func(i, j int) bool { + ni := r.pointers[i].Name() + nj := r.pointers[j].Name() + + if ni == nj { + return r.pointers[i].ID < r.pointers[j].ID + } + if ni == "" { + return false + } + if nj == "" { + return true + } + + return ni < nj + }) +}