// Copyright (c) Craftwork Games. All rights reserved.
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System;
using System.Runtime.InteropServices;
using MonoGame.Extended.Particles.Data;
namespace MonoGame.Extended.Particles;
///
/// Represents a circular memory buffer that efficiently stores and manages particles in contiguous unmanaged memory.
///
///
/// The class provides high-performance memory management for particle systems by
/// allocating an unmanaged memory block to store particle data in a circular buffer arrangement. This implementation
/// uses head and tail pointers to manage particle allocation and deallocation without requiring memory copying
/// operations, making it more efficient than a linear buffer approach.
///
public unsafe class ParticleBuffer : IDisposable
{
private readonly ParticleIterator _iterator;
///
/// Gets the native pointer to the beginning of the unmanaged memory buffer that stores particle data.
///
///
/// An pointing to the start of the allocated unmanaged memory block.
///
///
///
/// This property provides direct access to the underlying unmanaged memory used by the particle buffer.
/// The memory pointed to by this value contains particle data stored in a contiguous block, with each
/// particle occupying bytes.
///
///
/// Warning: This pointer should be used with extreme caution and only within unsafe code blocks.
/// Improper use of this pointer can lead to memory corruption, access violations, or other undefined behavior.
/// The memory is automatically managed by this instance and should not be
/// manually freed or modified outside of the provided safe methods.
///
///
/// The memory layout is arranged as a circular buffer where particles are stored sequentially.
/// Use and the for safe access to active particles rather than
/// directly manipulating this pointer.
///
///
/// This pointer becomes invalid after the is disposed. Accessing it after
/// disposal will result in undefined behavior.
///
///
public IntPtr NativePointer { get; }
///
/// Gets a pointer to the current tail position in the circular buffer where new particles are allocated.
///
public unsafe Particle* Tail { get; private set; }
///
/// Gets a pointer to the end fo the allocated buffer memory, used for circular buffer bounds checking.
///
public Particle* BufferEnd { get; }
///
/// Gets the maximum number of particles that can be stored in this buffer.
///
public int Size { get; }
///
/// Gets an iterator for traversing the active particles in the buffer.
///
///
/// The iterator is reset each time this property is accessed, starting from the current head position.
/// Use this to safely iterate through all active particles in the correct order.
///
public ParticleIterator Iterator => _iterator.Reset();
///
/// Gets a pointer to the current head position in the circular buffer where the oldest active particle is located.
///
///
/// This pointer should be used carefully and only within unsafe code blocks. The memory it points to is
/// automatically freed when the is disposed.
///
public unsafe Particle* Head { get; private set; }
///
/// Gets the number of additional particles that can be added to the buffer before it is full.
///
public int Available => Size - Count;
///
/// Gets the current number of active particles in the buffer.
///
public int Count { get; private set; }
///
/// Gets the total size of the buffer in bytes.
///
///
/// The size includes space for one additional particle beyond the specified capacity to facilitate
/// circular buffer operations.
///
public int SizeInBytes => Particle.SizeInBytes * (Size + 1);
///
/// Gets the size of the currently active portion of the buffer in bytes.
///
public int ActiveSizeInBytes => Particle.SizeInBytes * Count;
///
/// Gets a value indicating whether this has been disposed.
///
public bool IsDisposed { get; private set; }
///
/// Initializes a new instance of the class with the specified capacity.
///
/// The maximum number of particles that can be stored in the buffer.
public unsafe ParticleBuffer(int size)
{
Size = size;
NativePointer = Marshal.AllocHGlobal(SizeInBytes);
BufferEnd = (Particle*)(NativePointer + SizeInBytes);
Head = (Particle*)NativePointer;
Tail = (Particle*)NativePointer;
_iterator = new ParticleIterator(this);
GC.AddMemoryPressure(SizeInBytes);
}
///
~ParticleBuffer() => Dispose();
///
/// Allocates space in the circular buffer for a specified number of particles to be released.
///
/// The number of particles to allocate space for.
///
/// A positioned at the start of the newly allocated particles,
/// allowing iteration over the allocated particle slots.
///
///
/// Thrown if this method is called after the buffer has been disposed.
///
public unsafe ParticleIterator Release(int releaseQuantity)
{
ObjectDisposedException.ThrowIf(IsDisposed, this);
int numToRelease = Math.Min(releaseQuantity, Available);
int prevCount = Count;
Count += numToRelease;
Tail += numToRelease;
if (Tail >= BufferEnd)
{
Tail -= Size + 1;
}
return Iterator.Reset(prevCount);
}
///
/// Removes a specified number of particles from the beginning of the circular buffer.
///
/// The number of particles to remove from the buffer.
///
/// Thrown if this method is called after the buffer has been disposed.
///
public unsafe void Reclaim(int number)
{
ObjectDisposedException.ThrowIf(IsDisposed, this);
Count -= number;
Head += number;
if (Head >= BufferEnd)
{
Head -= Size + 1;
}
}
///
/// Releases all resources used by the .
///
public void Dispose()
{
if (IsDisposed)
{
return;
}
Marshal.FreeHGlobal(NativePointer);
GC.RemoveMemoryPressure(SizeInBytes);
IsDisposed = true;
GC.SuppressFinalize(this);
}
}