github.com/decred/dcrlnd@v0.7.6/queue/circular_buf.go (about)

     1  package queue
     2  
     3  import (
     4  	"errors"
     5  )
     6  
     7  // errInvalidSize is returned when an invalid size for a buffer is provided.
     8  var errInvalidSize = errors.New("buffer size must be > 0")
     9  
    10  // CircularBuffer is a buffer which retains a set of values in memory, and
    11  // overwrites the oldest item in the buffer when a new item needs to be
    12  // written.
    13  type CircularBuffer struct {
    14  	// total is the total number of items that have been added to the
    15  	// buffer.
    16  	total int
    17  
    18  	// items is the set of buffered items.
    19  	items []interface{}
    20  }
    21  
    22  // NewCircularBuffer returns a new circular buffer with the size provided. It
    23  // will fail if a zero or negative size parameter is provided.
    24  func NewCircularBuffer(size int) (*CircularBuffer, error) {
    25  	if size <= 0 {
    26  		return nil, errInvalidSize
    27  	}
    28  
    29  	return &CircularBuffer{
    30  		total: 0,
    31  
    32  		// Create a slice with length and capacity equal to the size of
    33  		// the buffer so that we do not need to resize the underlying
    34  		// array when we add items.
    35  		items: make([]interface{}, size),
    36  	}, nil
    37  }
    38  
    39  // index returns the index that should be written to next.
    40  func (c *CircularBuffer) index() int {
    41  	return c.total % len(c.items)
    42  }
    43  
    44  // Add adds an item to the buffer, overwriting the oldest item if the buffer
    45  // is full.
    46  func (c *CircularBuffer) Add(item interface{}) {
    47  	// Set the item in the next free index in the items array.
    48  	c.items[c.index()] = item
    49  
    50  	// Increment the total number of items that we have stored.
    51  	c.total++
    52  }
    53  
    54  // List returns a copy of the items in the buffer ordered from the oldest to
    55  // newest item.
    56  func (c *CircularBuffer) List() []interface{} {
    57  	size := cap(c.items)
    58  	index := c.index()
    59  
    60  	switch {
    61  	// If no items have been stored yet, we can just return a nil list.
    62  	case c.total == 0:
    63  		return nil
    64  
    65  	// If we have added fewer items than the buffer size, we can simply
    66  	// return the total number of items from the beginning of the list
    67  	// to the index. This special case is added because the oldest item
    68  	// is at the beginning of the underlying array, not at the index when
    69  	// we have not filled the array yet.
    70  	case c.total < size:
    71  		resp := make([]interface{}, c.total)
    72  		copy(resp, c.items[:c.index()])
    73  		return resp
    74  	}
    75  
    76  	resp := make([]interface{}, size)
    77  
    78  	// Get the items in the underlying array from index to end, the first
    79  	// item in this slice will be the oldest item in the list.
    80  	firstHalf := c.items[index:]
    81  
    82  	// Copy the first set into our response slice from index 0, so that
    83  	// the response returned is from oldest to newest.
    84  	copy(resp, firstHalf)
    85  
    86  	// Get the items in the underlying array from beginning until the write
    87  	// index, the last item in this slice will be the newest item in the
    88  	// list.
    89  	secondHalf := c.items[:index]
    90  
    91  	// Copy the second set of items into the response slice offset by the
    92  	// length of the first set of items so that we return a response which
    93  	// is ordered from oldest to newest entry.
    94  	copy(resp[len(firstHalf):], secondHalf)
    95  
    96  	return resp
    97  }
    98  
    99  // Total returns the total number of items that have been added to the buffer.
   100  func (c *CircularBuffer) Total() int {
   101  	return c.total
   102  }
   103  
   104  // Latest returns the item that was most recently added to the buffer.
   105  func (c *CircularBuffer) Latest() interface{} {
   106  	// If no items have been added yet, return nil.
   107  	if c.total == 0 {
   108  		return nil
   109  	}
   110  
   111  	// The latest item is one before our total, mod by length.
   112  	latest := (c.total - 1) % len(c.items)
   113  
   114  	// Return the latest item added.
   115  	return c.items[latest]
   116  }