// Ring buffer suballocator for per-frame dynamic GPU writes.
// Avoids per-frame buffer creation by cycling through a fixed buffer.

/// Ring buffer suballocator for per-frame dynamic GPU writes.
/// Avoids per-frame buffer creation by cycling through a fixed buffer.
pub struct RingBuffer {
    capacity: u64,
    write_offset: u64,
}

impl RingBuffer {
    pub fn new(capacity: u64) -> Self {
        Self {
            capacity,
            write_offset: 0,
        }
    }

    /// Allocate `size` bytes from the ring buffer, returning the byte offset.
    /// Returns `None` if the aligned size exceeds total capacity.
    /// Alignment is 256 bytes (wgpu uniform buffer offset alignment).
    pub fn allocate(&mut self, size: u64) -> Option<u64> {
        let aligned = (size + 255) & !255; // 256-byte alignment for uniform buffers
        if aligned > self.capacity {
            return None;
        }
        let offset = self.write_offset;
        self.write_offset = (self.write_offset + aligned) % self.capacity;
        Some(offset)
    }

    /// Reset the write head to the start. Called at frame boundaries.
    pub fn reset(&mut self) {
        self.write_offset = 0;
    }

    /// Total capacity in bytes.
    pub fn capacity(&self) -> u64 {
        self.capacity
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_ring_buffer() {
        let rb = RingBuffer::new(4096);
        assert_eq!(rb.capacity(), 4096);
    }

    #[test]
    fn allocate_returns_offset() {
        let mut rb = RingBuffer::new(4096);
        let off = rb.allocate(64).unwrap();
        assert_eq!(off, 0);
        // Next allocation starts at 256 (aligned)
        let off2 = rb.allocate(64).unwrap();
        assert_eq!(off2, 256);
    }

    #[test]
    fn allocate_wraps_around() {
        let mut rb = RingBuffer::new(512);
        let _ = rb.allocate(128).unwrap(); // offset 0, advances to 256
        let _ = rb.allocate(128).unwrap(); // offset 256, advances to 512 -> wraps to 0
        let off = rb.allocate(128).unwrap();
        assert_eq!(off, 0); // wrapped around
    }

    #[test]
    fn reset_rewinds_write_head() {
        let mut rb = RingBuffer::new(4096);
        rb.allocate(512).unwrap();
        rb.reset();
        let off = rb.allocate(64).unwrap();
        assert_eq!(off, 0);
    }

    #[test]
    fn over_capacity_returns_none() {
        let mut rb = RingBuffer::new(128);
        assert!(rb.allocate(256).is_none());
    }

    #[test]
    fn exact_capacity_succeeds() {
        let mut rb = RingBuffer::new(256);
        // 256-byte aligned request of 256 bytes fits exactly
        assert!(rb.allocate(256).is_some());
    }
}
