extends RefCounted class_name _HistoryBuffer # Maps ticks (int) to arbitrary data, stored in a sliding ring buffer var _capacity := 64 var _buffer := [] var _previous := [] var _tail := 0 var _head := 0 static func of(capacity: int, data: Dictionary) -> _HistoryBuffer: var history_buffer := _HistoryBuffer.new(capacity) for idx in data: history_buffer.set_at(idx, data[idx]) return history_buffer func _init(capacity: int = 64): _capacity = capacity _buffer.resize(_capacity) _previous.resize(_capacity) func duplicate(deep: bool = false) -> _HistoryBuffer: var buffer := _HistoryBuffer.new(_capacity) buffer._buffer = _buffer.duplicate(deep) buffer._previous = _previous.duplicate() buffer._tail = _tail buffer._head = _head return buffer func push(value: Variant) -> void: _buffer[_head % _capacity] = value _previous[_head % _capacity] = _head _head += 1 _tail += maxi(0, size() - capacity()) func pop() -> Variant: assert(is_not_empty(), "History buffer is empty!") var value = _buffer[_tail % _capacity] _tail += 1 return value func set_at(at: int, value: Variant) -> void: # Why does this need so many branches? if is_empty(): # Buffer is empty, jump to specified index _tail = at _head = at push(value) elif at < _head - capacity(): # Trying to set something that would wrap back around and overwrite # current data return elif at == _head: # Simply adding a new item push(value) elif at < _head: _buffer[at % _capacity] = value # Update prev-buffer for i in range(at, _head): if _previous[i % _capacity] == i: break _previous[i % _capacity] = at _tail = mini(_tail, at) elif at >= _head + _capacity: # We're leaving all data behind _tail = at _head = at _previous.fill(null) _buffer.fill(null) push(value) elif at >= _head: # Skipping forward a bit var previous := _head - 1 while _head < at: _previous[_head % _capacity] = previous _head += 1 _tail += maxi(0, size() - _capacity) push(value) func has_at(at: int) -> bool: if is_empty(): return false if at < _head - capacity(): return false if at >= _head: return false return _previous[at % _capacity] == at func get_at(at: int, default: Variant = null) -> Variant: if not has_at(at): return default return _buffer[at % _capacity] func has_latest_at(at: int) -> bool: if is_empty(): return false if at < _tail: return false return true func size() -> int: return _head - _tail func capacity() -> int: return _capacity func get_earliest_index() -> int: return _tail func get_latest_index() -> int: return _head - 1 func get_latest_index_at(at: int) -> int: if not has_latest_at(at): return -1 if at >= _head: return get_latest_index() return _previous[at % _capacity] func get_latest_at(at: int) -> Variant: return get_at(get_latest_index_at(at)) func clear(): _tail = _head func is_empty() -> bool: return size() == 0 func is_not_empty() -> bool: return not is_empty()