github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/common/prque/lazyqueue.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package prque 19 20 import ( 21 "container/heap" 22 "time" 23 24 "github.com/AigarNetwork/aigar/common/mclock" 25 ) 26 27 // LazyQueue is a priority queue data structure where priorities can change over 28 // time and are only evaluated on demand. 29 // Two callbacks are required: 30 // - priority evaluates the actual priority of an item 31 // - maxPriority gives an upper estimate for the priority in any moment between 32 // now and the given absolute time 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 } 49 50 type ( 51 PriorityCallback func(data interface{}, now mclock.AbsTime) 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), 59 setIndex: setIndex, 60 priority: priority, 61 maxPriority: maxPriority, 62 clock: clock, 63 period: refreshPeriod} 64 q.Reset() 65 q.Refresh() 66 return q 67 } 68 69 // Reset clears the contents of the queue 70 func (q *LazyQueue) Reset() { 71 q.queue[0] = newSstack(q.setIndex0) 72 q.queue[1] = newSstack(q.setIndex1) 73 } 74 75 // Refresh should be called at least with the frequency specified by the refreshPeriod parameter 76 func (q *LazyQueue) Refresh() { 77 q.maxUntil = q.clock.Now() + mclock.AbsTime(q.period) 78 for q.queue[0].Len() != 0 { 79 q.Push(heap.Pop(q.queue[0]).(*item).value) 80 } 81 q.queue[0], q.queue[1] = q.queue[1], q.queue[0] 82 q.indexOffset = 1 - q.indexOffset 83 q.maxUntil += mclock.AbsTime(q.period) 84 } 85 86 // Push adds an item to the queue 87 func (q *LazyQueue) Push(data interface{}) { 88 heap.Push(q.queue[1], &item{data, q.maxPriority(data, q.maxUntil)}) 89 } 90 91 // Update updates the upper priority estimate for the item with the given queue index 92 func (q *LazyQueue) Update(index int) { 93 q.Push(q.Remove(index)) 94 } 95 96 // Pop removes and returns the item with the greatest actual priority 97 func (q *LazyQueue) Pop() (interface{}, int64) { 98 var ( 99 resData interface{} 100 resPri int64 101 ) 102 q.MultiPop(func(data interface{}, priority int64) bool { 103 resData = data 104 resPri = priority 105 return false 106 }) 107 return resData, resPri 108 } 109 110 // peekIndex returns the index of the internal queue where the item with the 111 // highest estimated priority is or -1 if both are empty 112 func (q *LazyQueue) peekIndex() int { 113 if q.queue[0].Len() != 0 { 114 if q.queue[1].Len() != 0 && q.queue[1].blocks[0][0].priority > q.queue[0].blocks[0][0].priority { 115 return 1 116 } 117 return 0 118 } 119 if q.queue[1].Len() != 0 { 120 return 1 121 } 122 return -1 123 } 124 125 // MultiPop pops multiple items from the queue and is more efficient than calling 126 // Pop multiple times. Popped items are passed to the callback. MultiPop returns 127 // when the callback returns false or there are no more items to pop. 128 func (q *LazyQueue) MultiPop(callback func(data interface{}, priority int64) bool) { 129 now := q.clock.Now() 130 nextIndex := q.peekIndex() 131 for nextIndex != -1 { 132 data := heap.Pop(q.queue[nextIndex]).(*item).value 133 heap.Push(q.popQueue, &item{data, q.priority(data, now)}) 134 nextIndex = q.peekIndex() 135 for q.popQueue.Len() != 0 && (nextIndex == -1 || q.queue[nextIndex].blocks[0][0].priority < q.popQueue.blocks[0][0].priority) { 136 i := heap.Pop(q.popQueue).(*item) 137 if !callback(i.value, i.priority) { 138 for q.popQueue.Len() != 0 { 139 q.Push(heap.Pop(q.popQueue).(*item).value) 140 } 141 return 142 } 143 } 144 } 145 } 146 147 // PopItem pops the item from the queue only, dropping the associated priority value. 148 func (q *LazyQueue) PopItem() interface{} { 149 i, _ := q.Pop() 150 return i 151 } 152 153 // Remove removes removes the item with the given index. 154 func (q *LazyQueue) Remove(index int) interface{} { 155 if index < 0 { 156 return nil 157 } 158 return heap.Remove(q.queue[index&1^q.indexOffset], index>>1).(*item).value 159 } 160 161 // Empty checks whether the priority queue is empty. 162 func (q *LazyQueue) Empty() bool { 163 return q.queue[0].Len() == 0 && q.queue[1].Len() == 0 164 } 165 166 // Size returns the number of items in the priority queue. 167 func (q *LazyQueue) Size() int { 168 return q.queue[0].Len() + q.queue[1].Len() 169 } 170 171 // setIndex0 translates internal queue item index to the virtual index space of LazyQueue 172 func (q *LazyQueue) setIndex0(data interface{}, index int) { 173 if index == -1 { 174 q.setIndex(data, -1) 175 } else { 176 q.setIndex(data, index+index) 177 } 178 } 179 180 // setIndex1 translates internal queue item index to the virtual index space of LazyQueue 181 func (q *LazyQueue) setIndex1(data interface{}, index int) { 182 q.setIndex(data, index+index+1) 183 }