// 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); } }