diff --git a/internal/server/aof.go b/internal/server/aof.go index 83913e01..40119ed1 100644 --- a/internal/server/aof.go +++ b/internal/server/aof.go @@ -15,7 +15,6 @@ import ( "time" "github.com/tidwall/buntdb" - "github.com/tidwall/geojson/geometry" "github.com/tidwall/gjson" "github.com/tidwall/redcon" "github.com/tidwall/resp" @@ -227,55 +226,70 @@ func (s *Server) writeAOF(args []string, d *commandDetails) error { } func (s *Server) getQueueCandidates(d *commandDetails) []*Hook { - var candidates []*Hook + candidates := make(map[*Hook]bool) // add the hooks with "outside" detection - if len(s.hooksOut) > 0 { - for _, hook := range s.hooksOut { - if hook.Key == d.key { - candidates = append(candidates, hook) - } + for _, hook := range s.hooksOut { + if hook.Key == d.key { + candidates[hook] = true } } - // search the hook spatial tree - // build a rectangle that fills the old and new which will be enough to - // handle "enter", "inside", "exit", and "cross" detections. - var rect geometry.Rect - if d.oldObj != nil { - rect = d.oldObj.Rect() - if d.obj != nil { - r2 := d.obj.Rect() - rect.Min.X = math.Min(rect.Min.X, r2.Min.X) - rect.Min.Y = math.Min(rect.Min.Y, r2.Min.Y) - rect.Max.X = math.Max(rect.Max.X, r2.Max.X) - rect.Max.Y = math.Max(rect.Max.Y, r2.Max.Y) - } - } else if d.obj != nil { - rect = d.obj.Rect() - } else { - return candidates - } - s.hookTree.Search( - [2]float64{rect.Min.X, rect.Min.Y}, - [2]float64{rect.Max.X, rect.Max.Y}, - func(_, _ [2]float64, value interface{}) bool { - hook := value.(*Hook) - if hook.Key != d.key { - return true - } - var found bool - for _, candidate := range candidates { - if candidate == hook { - found = true - break + // look for candidates that might "cross" geofences + if d.oldObj != nil && d.obj != nil && s.hookCross.Len() > 0 { + r1, r2 := d.oldObj.Rect(), d.obj.Rect() + s.hookCross.Search( + [2]float64{ + math.Min(r1.Min.X, r2.Min.X), + math.Min(r1.Min.Y, r2.Min.Y), + }, + [2]float64{ + math.Max(r1.Max.X, r2.Max.X), + math.Max(r1.Max.Y, r2.Max.Y), + }, + func(min, max [2]float64, value interface{}) bool { + hook := value.(*Hook) + if hook.Key == d.key { + candidates[hook] = true } - } - if !found { - candidates = append(candidates, hook) - } - return true - }, - ) - return candidates + return true + }) + } + // look for candidates that overlap the old object + if d.oldObj != nil { + r1 := d.oldObj.Rect() + s.hookTree.Search( + [2]float64{r1.Min.X, r1.Min.Y}, + [2]float64{r1.Max.X, r1.Max.Y}, + func(min, max [2]float64, value interface{}) bool { + hook := value.(*Hook) + if hook.Key == d.key { + candidates[hook] = true + } + return true + }) + } + // look for candidates that overlap the new object + if d.obj != nil { + r1 := d.obj.Rect() + s.hookTree.Search( + [2]float64{r1.Min.X, r1.Min.Y}, + [2]float64{r1.Max.X, r1.Max.Y}, + func(min, max [2]float64, value interface{}) bool { + hook := value.(*Hook) + if hook.Key == d.key { + candidates[hook] = true + } + return true + }) + } + if len(candidates) == 0 { + return nil + } + // return the candidates as a slice + ret := make([]*Hook, 0, len(candidates)) + for hook := range candidates { + ret = append(ret, hook) + } + return ret } func (s *Server) queueHooks(d *commandDetails) error { diff --git a/internal/server/crud.go b/internal/server/crud.go index 3802c493..78fca51a 100644 --- a/internal/server/crud.go +++ b/internal/server/crud.go @@ -514,6 +514,7 @@ func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetails server.hooks = make(map[string]*Hook) server.hooksOut = make(map[string]*Hook) server.hookTree = rbang.RTree{} + server.hookCross = rbang.RTree{} d.command = "flushdb" d.updated = true d.timestamp = time.Now() diff --git a/internal/server/hooks.go b/internal/server/hooks.go index 9446775c..9153e3b2 100644 --- a/internal/server/hooks.go +++ b/internal/server/hooks.go @@ -197,6 +197,12 @@ func (s *Server) cmdSetHook(msg *Message, chanCmd bool) ( [2]float64{rect.Min.X, rect.Min.Y}, [2]float64{rect.Max.X, rect.Max.Y}, prevHook) + if prevHook.Fence.detect["cross"] { + s.hookCross.Delete( + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, + prevHook) + } } // add hook to spatial index if hook != nil && hook.Fence != nil && hook.Fence.obj != nil { @@ -205,6 +211,12 @@ func (s *Server) cmdSetHook(msg *Message, chanCmd bool) ( [2]float64{rect.Min.X, rect.Min.Y}, [2]float64{rect.Max.X, rect.Max.Y}, hook) + if hook.Fence.detect["cross"] { + s.hookCross.Insert( + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, + hook) + } } hook.Open() // Opens a goroutine to notify the hook @@ -246,6 +258,12 @@ func (s *Server) cmdDelHook(msg *Message, chanCmd bool) ( [2]float64{rect.Min.X, rect.Min.Y}, [2]float64{rect.Max.X, rect.Max.Y}, hook) + if hook.Fence.detect["cross"] { + s.hookCross.Delete( + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, + hook) + } } d.updated = true } @@ -298,6 +316,12 @@ func (s *Server) cmdPDelHook(msg *Message, channel bool) ( [2]float64{rect.Min.X, rect.Min.Y}, [2]float64{rect.Max.X, rect.Max.Y}, hook) + if hook.Fence.detect["cross"] { + s.hookCross.Delete( + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, + hook) + } } d.updated = true count++ diff --git a/internal/server/server.go b/internal/server/server.go index cd29b1a8..1ac710e6 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -115,7 +115,8 @@ type Server struct { shrinking bool // aof shrinking flag shrinklog [][]string // aof shrinking log hooks map[string]*Hook // hook name - hookTree rbang.RTree // hook spatial tree containing all + hookCross rbang.RTree // hook spatial tree for "cross" geofences + hookTree rbang.RTree // hook spatial tree for all hooksOut map[string]*Hook // hooks with "outside" detection aofconnM map[net.Conn]bool luascripts *lScriptMap