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