| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- // 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;
- /// <summary>
- /// Represents a circular memory buffer that efficiently stores and manages particles in contiguous unmanaged memory.
- /// </summary>
- /// <remarks>
- /// The <see cref="ParticleBuffer"/> 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.
- /// </remarks>
- public unsafe class ParticleBuffer : IDisposable
- {
- private readonly ParticleIterator _iterator;
- /// <summary>
- /// Gets the native pointer to the beginning of the unmanaged memory buffer that stores particle data.
- /// </summary>
- /// <value>
- /// An <see cref="IntPtr"/> pointing to the start of the allocated unmanaged memory block.
- /// </value>
- /// <remarks>
- /// <para>
- /// 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 <see cref="Particle.SizeInBytes"/> bytes.
- /// </para>
- /// <para>
- /// <strong>Warning:</strong> 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 <see cref="ParticleBuffer"/> instance and should not be
- /// manually freed or modified outside of the provided safe methods.
- /// </para>
- /// <para>
- /// The memory layout is arranged as a circular buffer where particles are stored sequentially.
- /// Use <see cref="Head"/> and the <see cref="Iterator"/> for safe access to active particles rather than
- /// directly manipulating this pointer.
- /// </para>
- /// <para>
- /// This pointer becomes invalid after the <see cref="ParticleBuffer"/> is disposed. Accessing it after
- /// disposal will result in undefined behavior.
- /// </para>
- /// </remarks>
- public IntPtr NativePointer { get; }
- /// <summary>
- /// Gets a pointer to the current tail position in the circular buffer where new particles are allocated.
- /// </summary>
- public unsafe Particle* Tail { get; private set; }
- /// <summary>
- /// Gets a pointer to the end fo the allocated buffer memory, used for circular buffer bounds checking.
- /// </summary>
- public Particle* BufferEnd { get; }
- /// <summary>
- /// Gets the maximum number of particles that can be stored in this buffer.
- /// </summary>
- public int Size { get; }
- /// <summary>
- /// Gets an iterator for traversing the active particles in the buffer.
- /// </summary>
- /// <remarks>
- /// 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.
- /// </remarks>
- public ParticleIterator Iterator => _iterator.Reset();
- /// <summary>
- /// Gets a pointer to the current head position in the circular buffer where the oldest active particle is located.
- /// </summary>
- /// <remarks>
- /// This pointer should be used carefully and only within unsafe code blocks. The memory it points to is
- /// automatically freed when the <see cref="ParticleBuffer"/> is disposed.
- /// </remarks>
- public unsafe Particle* Head { get; private set; }
- /// <summary>
- /// Gets the number of additional particles that can be added to the buffer before it is full.
- /// </summary>
- public int Available => Size - Count;
- /// <summary>
- /// Gets the current number of active particles in the buffer.
- /// </summary>
- public int Count { get; private set; }
- /// <summary>
- /// Gets the total size of the buffer in bytes.
- /// </summary>
- /// <remarks>
- /// The size includes space for one additional particle beyond the specified capacity to facilitate
- /// circular buffer operations.
- /// </remarks>
- public int SizeInBytes => Particle.SizeInBytes * (Size + 1);
- /// <summary>
- /// Gets the size of the currently active portion of the buffer in bytes.
- /// </summary>
- public int ActiveSizeInBytes => Particle.SizeInBytes * Count;
- /// <summary>
- /// Gets a value indicating whether this <see cref="ParticleBuffer"/> has been disposed.
- /// </summary>
- public bool IsDisposed { get; private set; }
- /// <summary>
- /// Initializes a new instance of the <see cref="ParticleBuffer"/> class with the specified capacity.
- /// </summary>
- /// <param name="size">The maximum number of particles that can be stored in the buffer.</param>
- 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);
- }
- /// <summary/>
- ~ParticleBuffer() => Dispose();
- /// <summary>
- /// Allocates space in the circular buffer for a specified number of particles to be released.
- /// </summary>
- /// <param name="releaseQuantity">The number of particles to allocate space for.</param>
- /// <returns>
- /// A <see cref="ParticleIterator"/> positioned at the start of the newly allocated particles,
- /// allowing iteration over the allocated particle slots.
- /// </returns>
- /// <exception cref="ObjectDisposedException">
- /// Thrown if this method is called after the buffer has been disposed.
- /// </exception>
- 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);
- }
- /// <summary>
- /// Removes a specified number of particles from the beginning of the circular buffer.
- /// </summary>
- /// <param name="number">The number of particles to remove from the buffer.</param>
- /// <exception cref="ObjectDisposedException">
- /// Thrown if this method is called after the buffer has been disposed.
- /// </exception>
- public unsafe void Reclaim(int number)
- {
- ObjectDisposedException.ThrowIf(IsDisposed, this);
- Count -= number;
- Head += number;
- if (Head >= BufferEnd)
- {
- Head -= Size + 1;
- }
- }
- /// <summary>
- /// Releases all resources used by the <see cref="ParticleBuffer"/>.
- /// </summary>
- public void Dispose()
- {
- if (IsDisposed)
- {
- return;
- }
- Marshal.FreeHGlobal(NativePointer);
- GC.RemoveMemoryPressure(SizeInBytes);
- IsDisposed = true;
- GC.SuppressFinalize(this);
- }
- }
|