/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. */ using System; using UnityEngine; namespace Meta.WitAi.Data { public class RingBuffer { public delegate void OnDataAdded(T[] data, int offset, int length); public delegate void ByteDataWriter(T[] buffer, int offset, int length); public OnDataAdded OnDataAddedEvent; private readonly T[] buffer; private int bufferIndex; private long bufferDataLength; public int Capacity => buffer.Length; public int GetBufferArrayIndex(long bufferDataIndex) { if (bufferDataLength <= bufferDataIndex) return -1; if (bufferDataLength - bufferDataIndex > buffer.Length) return -1; var endOffset = bufferDataLength - bufferDataIndex; var index = bufferIndex - endOffset; if (index < 0) index = buffer.Length + index; return (int) index; } public T this[long bufferDataIndex] => buffer[GetBufferArrayIndex(bufferDataIndex)]; public void Clear(bool eraseData = false) { bufferIndex = 0; bufferDataLength = 0; if (eraseData) { for (int i = 0; i < buffer.Length; i++) { buffer[i] = default; } } } public class Marker { private long bufferDataIndex; private int index; private readonly RingBuffer ringBuffer; public RingBuffer RingBuffer => ringBuffer; public Marker(RingBuffer ringBuffer, long markerPosition, int bufIndex) { this.ringBuffer = ringBuffer; bufferDataIndex = markerPosition; index = bufIndex; } public bool IsValid => ringBuffer.bufferDataLength - bufferDataIndex <= ringBuffer.Capacity; public long AvailableByteCount => Math.Min(ringBuffer.Capacity, RequestedByteCount); public long RequestedByteCount => ringBuffer.bufferDataLength - bufferDataIndex; public long CurrentBufferDataIndex => bufferDataIndex; public int Read(T[] buffer, int offset, int length, bool skipToNextValid = false) { int read = -1; if (!IsValid && skipToNextValid && ringBuffer.bufferDataLength > ringBuffer.Capacity) { bufferDataIndex = ringBuffer.bufferDataLength - ringBuffer.Capacity; } if (IsValid) { read = this.ringBuffer.Read(buffer, offset, length, bufferDataIndex); bufferDataIndex += read; index += read; if (index > buffer.Length) index -= buffer.Length; } return read; } public void ReadIntoWriters(params ByteDataWriter[] writers) { if (!IsValid && ringBuffer.bufferDataLength > ringBuffer.Capacity) { bufferDataIndex = ringBuffer.bufferDataLength - ringBuffer.Capacity; } index = ringBuffer.GetBufferArrayIndex(bufferDataIndex); var length = (int) (ringBuffer.bufferDataLength - bufferDataIndex); if (IsValid && length > 0) { for (int i = 0; i < writers.Length; i++) { ringBuffer.WriteFromBuffer(writers[i], index, length); } } bufferDataIndex += length; index = ringBuffer.GetBufferArrayIndex(bufferDataIndex); } public Marker Clone() { return new Marker(ringBuffer, bufferDataIndex, index); } public void Offset(int amount) { bufferDataIndex += amount; if (bufferDataIndex < 0) bufferDataIndex = 0; if (bufferDataIndex > ringBuffer.bufferDataLength) { bufferDataIndex = ringBuffer.bufferDataLength; } index = ringBuffer.GetBufferArrayIndex(bufferDataIndex); } } public RingBuffer(int capacity) { buffer = new T[capacity]; } private int CopyToBuffer(T[] data, int offset, int length, int bufferIndex) { if (length > buffer.Length) throw new ArgumentException( "Push data exceeds buffer size."); if (bufferIndex + length < buffer.Length) { Array.Copy(data, offset, buffer, bufferIndex, length); return bufferIndex + length; } else { int len = Mathf.Min(length, buffer.Length); int endChunkLength = buffer.Length - bufferIndex; int wrappedChunkLength = len - endChunkLength; try { Array.Copy(data, offset, buffer, bufferIndex, endChunkLength); Array.Copy(data, offset + endChunkLength, buffer, 0, wrappedChunkLength); return wrappedChunkLength; } catch (ArgumentException e) { throw e; } } } public void WriteFromBuffer(ByteDataWriter writer, long bufferIndex, int length) { lock (buffer) { if (bufferIndex + length < buffer.Length) { writer(buffer, (int) bufferIndex, length); } else { if (length > bufferDataLength) { length = (int) (bufferDataLength - bufferIndex); } if (length > buffer.Length) { length = buffer.Length; } var l = Math.Min(buffer.Length, length); int endChunkLength = (int) (buffer.Length - bufferIndex); int wrappedChunkLength = l - endChunkLength; writer(buffer, (int) bufferIndex, endChunkLength); writer(buffer, 0, wrappedChunkLength); } } } private int CopyFromBuffer(T[] data, int offset, int length, int bufferIndex) { if (length > buffer.Length) throw new ArgumentException( $"Push data exceeds buffer size {length} < {buffer.Length}" ); if (bufferIndex + length < buffer.Length) { Array.Copy(buffer, bufferIndex, data, offset, length); return bufferIndex + length; } else { var l = Mathf.Min(buffer.Length, length); int endChunkLength = buffer.Length - bufferIndex; int wrappedChunkLength = l - endChunkLength; Array.Copy(buffer, bufferIndex, data, offset, endChunkLength); Array.Copy(buffer, 0, data, offset + endChunkLength, wrappedChunkLength); return wrappedChunkLength; } } public void Push(T[] data, int offset, int length) { lock (buffer) { bufferIndex = CopyToBuffer(data, offset, length, bufferIndex); bufferDataLength += length; OnDataAddedEvent?.Invoke(data, offset, length); } } public void Push(T data) { lock (buffer) { buffer[bufferIndex++] = data; if (bufferIndex >= buffer.Length) { bufferIndex = 0; } bufferDataLength++; } } public int Read(T[] data, int offset, int length, long bufferDataIndex) { if (bufferIndex == 0 && bufferDataLength == 0) // The ring buffer has been cleared. { return 0; } lock (buffer) { int read = (int) (Math.Min(bufferDataIndex + length, bufferDataLength) - bufferDataIndex); int bufferIndex = this.bufferIndex - (int) (bufferDataLength - bufferDataIndex); if (bufferIndex < 0) { bufferIndex = buffer.Length + bufferIndex; } CopyFromBuffer(data, offset, length, bufferIndex); return read; } } public Marker CreateMarker(int offset = 0) { var markerPosition = bufferDataLength + offset; if (markerPosition < 0) { markerPosition = 0; } int bufIndex = bufferIndex + offset; if (bufIndex < 0) { bufIndex = buffer.Length + bufIndex; } if (bufIndex > buffer.Length) { bufIndex -= buffer.Length; } var marker = new Marker(this, markerPosition, bufIndex); return marker; } } }