github.com/theQRL/go-zond@v0.1.1/common/prque/lazyqueue.go (about)

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