diff --git a/app.go b/app.go index 3f3eb68..e1af8a9 100644 --- a/app.go +++ b/app.go @@ -13,9 +13,9 @@ import ( "github.com/gorilla/websocket" sunriseLib "github.com/nathan-osman/go-sunrise" + "github.com/Workiva/go-datastructures/queue" "github.com/Xevion/gome-assistant/internal" "github.com/Xevion/gome-assistant/internal/http" - pq "github.com/Xevion/gome-assistant/internal/priority_queue" ws "github.com/Xevion/gome-assistant/internal/websocket" ) @@ -34,13 +34,27 @@ type App struct { service *Service state *StateImpl - schedules pq.PriorityQueue - intervals pq.PriorityQueue + schedules *queue.PriorityQueue + intervals *queue.PriorityQueue entityListeners map[string][]*EntityListener entityListenersId int64 eventListeners map[string][]*EventListener } +type Item struct { + Value interface{} + Priority float64 +} + +func (mi Item) Compare(other queue.Item) int { + if mi.Priority > other.(Item).Priority { + return 1 + } else if mi.Priority == other.(Item).Priority { + return 0 + } + return -1 +} + // DurationString represents a duration, such as "2s" or "24h". // See https://pkg.go.dev/time#ParseDuration for all valid time units. type DurationString string @@ -178,8 +192,8 @@ func NewApp(request NewAppRequest) (*App, error) { httpClient: httpClient, service: service, state: state, - schedules: pq.New(), - intervals: pq.New(), + schedules: queue.NewPriorityQueue(100, false), + intervals: queue.NewPriorityQueue(100, false), entityListeners: map[string][]*EntityListener{}, eventListeners: map[string][]*EventListener{}, }, nil @@ -232,7 +246,7 @@ func (a *App) RegisterSchedules(schedules ...DailySchedule) { // realStartTime already set for sunset/sunrise if s.isSunrise || s.isSunset { s.nextRunTime = getNextSunRiseOrSet(a, s.isSunrise, s.sunOffset).Carbon2Time() - a.schedules.Insert(s, float64(s.nextRunTime.Unix())) + a.schedules.Put() continue } @@ -245,7 +259,10 @@ func (a *App) RegisterSchedules(schedules ...DailySchedule) { } s.nextRunTime = startTime.Carbon2Time() - a.schedules.Insert(s, float64(startTime.Carbon2Time().Unix())) + a.schedules.Put(Item{ + Value: s, + Priority: float64(startTime.Carbon2Time().Unix()), + }) } } @@ -261,7 +278,10 @@ func (a *App) RegisterIntervals(intervals ...Interval) { for i.nextRunTime.Before(now) { i.nextRunTime = i.nextRunTime.Add(i.frequency) } - a.intervals.Insert(i, float64(i.nextRunTime.Unix())) + a.intervals.Put(Item{ + Value: i, + Priority: float64(i.nextRunTime.Unix()), + }) } } diff --git a/go.mod b/go.mod index 52a8c5a..527f19e 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,11 @@ module github.com/Xevion/gome-assistant go 1.21 require ( + github.com/Workiva/go-datastructures v1.1.5 github.com/golang-module/carbon v1.7.1 github.com/gorilla/websocket v1.5.0 github.com/nathan-osman/go-sunrise v1.1.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.10.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index f4fa6c1..0fdb7cd 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4= +github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -41,6 +43,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/nathan-osman/go-sunrise v1.1.0 h1:ZqZmtmtzs8Os/DGQYi0YMHpuUqR/iRoJK+wDO0wTCw8= github.com/nathan-osman/go-sunrise v1.1.0/go.mod h1:RcWqhT+5ShCZDev79GuWLayetpJp78RSjSWxiDowmlM= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -64,23 +67,40 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/priority_queue/priority_queue.go b/internal/priority_queue/priority_queue.go deleted file mode 100644 index c51918a..0000000 --- a/internal/priority_queue/priority_queue.go +++ /dev/null @@ -1,87 +0,0 @@ -package priority_queue - -import ( - "container/heap" - "errors" -) - -// PriorityQueue represents the queue -type PriorityQueue struct { - itemHeap *itemHeap - lookup map[interface{}]*item -} - -// New initializes an empty priority queue. -func New() PriorityQueue { - return PriorityQueue{ - itemHeap: &itemHeap{}, - lookup: make(map[interface{}]*item), - } -} - -// Len returns the number of elements in the queue. -func (p *PriorityQueue) Len() int { - return p.itemHeap.Len() -} - -// Insert inserts a new element into the queue. No action is performed on duplicate elements. -func (p *PriorityQueue) Insert(v interface{ Hash() string }, priority float64) { - _, ok := p.lookup[v.Hash()] - if ok { - return - } - - newItem := &item{ - value: v, - priority: priority, - } - heap.Push(p.itemHeap, newItem) - p.lookup[v.Hash()] = newItem -} - -// Pop removes the element with the highest priority from the queue and returns it. -// In case of an empty queue, an error is returned. -func (p *PriorityQueue) Pop() (interface{}, error) { - if len(*p.itemHeap) == 0 { - return nil, errors.New("empty queue") - } - - item := heap.Pop(p.itemHeap).(*item) - delete(p.lookup, item.value.(interface{ Hash() string }).Hash()) - return item.value, nil -} - -type itemHeap []*item - -type item struct { - value interface{} - priority float64 - index int -} - -func (ih *itemHeap) Len() int { - return len(*ih) -} - -func (ih *itemHeap) Less(i, j int) bool { - return (*ih)[i].priority < (*ih)[j].priority -} - -func (ih *itemHeap) Swap(i, j int) { - (*ih)[i], (*ih)[j] = (*ih)[j], (*ih)[i] - (*ih)[i].index = i - (*ih)[j].index = j -} - -func (ih *itemHeap) Push(x interface{}) { - it := x.(*item) - it.index = len(*ih) - *ih = append(*ih, it) -} - -func (ih *itemHeap) Pop() interface{} { - old := *ih - item := old[len(old)-1] - *ih = old[0 : len(old)-1] - return item -} diff --git a/interval.go b/interval.go index c076354..22a4d8e 100644 --- a/interval.go +++ b/interval.go @@ -206,12 +206,15 @@ func (i Interval) maybeRunCallback(a *App) { } func popInterval(a *App) Interval { - i, _ := a.intervals.Pop() - return i.(Interval) + i, _ := a.intervals.Get(1) + return i[0].(Item).Value.(Interval) } func requeueInterval(a *App, i Interval) { i.nextRunTime = i.nextRunTime.Add(i.frequency) - a.intervals.Insert(i, float64(i.nextRunTime.Unix())) + a.intervals.Put(Item{ + Value: i, + Priority: float64(i.nextRunTime.Unix()), + }) } diff --git a/schedule.go b/schedule.go index 34e45b1..6483f62 100644 --- a/schedule.go +++ b/schedule.go @@ -208,8 +208,8 @@ func (s DailySchedule) maybeRunCallback(a *App) { } func popSchedule(a *App) DailySchedule { - _sched, _ := a.schedules.Pop() - return _sched.(DailySchedule) + _sched, _ := a.schedules.Get(1) + return _sched[0].(Item).Value.(DailySchedule) } func requeueSchedule(a *App, s DailySchedule) { @@ -227,5 +227,8 @@ func requeueSchedule(a *App, s DailySchedule) { s.nextRunTime = carbon.Time2Carbon(s.nextRunTime).AddDay().Carbon2Time() } - a.schedules.Insert(s, float64(s.nextRunTime.Unix())) + a.schedules.Put(Item{ + Value: s, + Priority: float64(s.nextRunTime.Unix()), + }) }