github.com/amazechain/amc@v0.1.3/common/prque/lazyqueue.go (about) 1 // Copyright 2023 The AmazeChain Authors 2 // This file is part of the AmazeChain library. 3 // 4 // The AmazeChain library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The AmazeChain library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the AmazeChain library. If not, see <http://www.gnu.org/licenses/>. 16 17 package prque 18 19 import ( 20 "container/heap" 21 "github.com/amazechain/amc/common/mclock" 22 "time" 23 ) 24 25 // LazyQueue is a priority queue data structure where priorities can change over 26 // time and are only evaluated on demand. 27 // Two callbacks are required: 28 // - priority evaluates the actual priority of an item 29 // - maxPriority gives an upper estimate for the priority in any moment between 30 // now and the given absolute time 31 // 32 // If the upper estimate is exceeded then Update should be called for that item. 33 // A global Refresh function should also be called periodically. 34 type LazyQueue struct { 35 clock mclock.Clock 36 // Items are stored in one of two internal queues ordered by estimated max 37 // priority until the next and the next-after-next refresh. Update and Refresh 38 // always places items in queue[1]. 39 queue [2]*sstack 40 popQueue *sstack 41 period time.Duration 42 maxUntil mclock.AbsTime 43 indexOffset int 44 setIndex SetIndexCallback 45 priority PriorityCallback 46 maxPriority MaxPriorityCallback 47 lastRefresh1, lastRefresh2 mclock.AbsTime 48 } 49 50 type ( 51 PriorityCallback func(data interface{}) int64 // actual priority callback 52 MaxPriorityCallback func(data interface{}, until mclock.AbsTime) int64 // estimated maximum priority callback 53 ) 54 55 // NewLazyQueue creates a new lazy queue 56 func NewLazyQueue(setIndex SetIndexCallback, priority PriorityCallback, maxPriority MaxPriorityCallback, clock mclock.Clock, refreshPeriod time.Duration) *LazyQueue { 57 q := &LazyQueue{ 58 popQueue: newSstack(nil, false), 59 setIndex: setIndex, 60 priority: priority, 61 maxPriority: maxPriority, 62 clock: clock, 63 period: refreshPeriod, 64 lastRefresh1: clock.Now(), 65 lastRefresh2: clock.Now(), 66 } 67 q.Reset() 68 q.refresh(clock.Now()) 69 return q 70 } 71 72 // Reset clears the contents of the queue 73 func (q *LazyQueue) Reset() { 74 q.queue[0] = newSstack(q.setIndex0, false) 75 q.queue[1] = newSstack(q.setIndex1, false) 76 } 77 78 // Refresh performs queue re-evaluation if necessary 79 func (q *LazyQueue) Refresh() { 80 now := q.clock.Now() 81 for time.Duration(now-q.lastRefresh2) >= q.period*2 { 82 q.refresh(now) 83 q.lastRefresh2 = q.lastRefresh1 84 q.lastRefresh1 = now 85 } 86 } 87 88 // refresh re-evaluates items in the older queue and swaps the two queues 89 func (q *LazyQueue) refresh(now mclock.AbsTime) { 90 q.maxUntil = now + mclock.AbsTime(q.period) 91 for q.queue[0].Len() != 0 { 92 q.Push(heap.Pop(q.queue[0]).(*item).value) 93 } 94 q.queue[0], q.queue[1] = q.queue[1], q.queue[0] 95 q.indexOffset = 1 - q.indexOffset 96 q.maxUntil += mclock.AbsTime(q.period) 97 } 98 99 // Push adds an item to the queue 100 func (q *LazyQueue) Push(data interface{}) { 101 heap.Push(q.queue[1], &item{data, q.maxPriority(data, q.maxUntil)}) 102 } 103 104 // Update updates the upper priority estimate for the item with the given queue index 105 func (q *LazyQueue) Update(index int) { 106 q.Push(q.Remove(index)) 107 } 108 109 // Pop removes and returns the item with the greatest actual priority 110 func (q *LazyQueue) Pop() (interface{}, int64) { 111 var ( 112 resData interface{} 113 resPri int64 114 ) 115 q.MultiPop(func(data interface{}, priority int64) bool { 116 resData = data 117 resPri = priority 118 return false 119 }) 120 return resData, resPri 121 } 122 123 // peekIndex returns the index of the internal queue where the item with the 124 // highest estimated priority is or -1 if both are empty 125 func (q *LazyQueue) peekIndex() int { 126 if q.queue[0].Len() != 0 { 127 if q.queue[1].Len() != 0 && q.queue[1].blocks[0][0].priority > q.queue[0].blocks[0][0].priority { 128 return 1 129 } 130 return 0 131 } 132 if q.queue[1].Len() != 0 { 133 return 1 134 } 135 return -1 136 } 137 138 // MultiPop pops multiple items from the queue and is more efficient than calling 139 // Pop multiple times. Popped items are passed to the callback. MultiPop returns 140 // when the callback returns false or there are no more items to pop. 141 func (q *LazyQueue) MultiPop(callback func(data interface{}, priority int64) bool) { 142 nextIndex := q.peekIndex() 143 for nextIndex != -1 { 144 data := heap.Pop(q.queue[nextIndex]).(*item).value 145 heap.Push(q.popQueue, &item{data, q.priority(data)}) 146 nextIndex = q.peekIndex() 147 for q.popQueue.Len() != 0 && (nextIndex == -1 || q.queue[nextIndex].blocks[0][0].priority < q.popQueue.blocks[0][0].priority) { 148 i := heap.Pop(q.popQueue).(*item) 149 if !callback(i.value, i.priority) { 150 for q.popQueue.Len() != 0 { 151 q.Push(heap.Pop(q.popQueue).(*item).value) 152 } 153 return 154 } 155 nextIndex = q.peekIndex() // re-check because callback is allowed to push items back 156 } 157 } 158 } 159 160 // PopItem pops the item from the queue only, dropping the associated priority value. 161 func (q *LazyQueue) PopItem() interface{} { 162 i, _ := q.Pop() 163 return i 164 } 165 166 // Remove removes removes the item with the given index. 167 func (q *LazyQueue) Remove(index int) interface{} { 168 if index < 0 { 169 return nil 170 } 171 return heap.Remove(q.queue[index&1^q.indexOffset], index>>1).(*item).value 172 } 173 174 // Empty checks whether the priority queue is empty. 175 func (q *LazyQueue) Empty() bool { 176 return q.queue[0].Len() == 0 && q.queue[1].Len() == 0 177 } 178 179 // Size returns the number of items in the priority queue. 180 func (q *LazyQueue) Size() int { 181 return q.queue[0].Len() + q.queue[1].Len() 182 } 183 184 // setIndex0 translates internal queue item index to the virtual index space of LazyQueue 185 func (q *LazyQueue) setIndex0(data interface{}, index int) { 186 if index == -1 { 187 q.setIndex(data, -1) 188 } else { 189 q.setIndex(data, index+index) 190 } 191 } 192 193 // setIndex1 translates internal queue item index to the virtual index space of LazyQueue 194 func (q *LazyQueue) setIndex1(data interface{}, index int) { 195 q.setIndex(data, index+index+1) 196 }