CI / build-test (push) Failing after 1m8s
Add MrGameEng.AI utility-AI module; format codebase with CSharpier New MrGameEng.AI module (ResponseCurve, Consideration, UtilityAction, UtilityAi selector, Blackboard) plus CSharpier formatting applied across the whole engine. Documents the CSharpier convention in CLAUDE.md. @
95 lines
3.3 KiB
C#
95 lines
3.3 KiB
C#
namespace MrGameEng.Graphics;
|
|
|
|
/// <summary>Compact identifier of a render layer. Obtained from <see cref="LayerRegistry.Register"/>.</summary>
|
|
public readonly record struct LayerId(byte Value)
|
|
{
|
|
/// <summary>The default layer (the first one registered).</summary>
|
|
public static readonly LayerId Default = new(0);
|
|
}
|
|
|
|
/// <summary>Coordinate space a layer is drawn in.</summary>
|
|
public enum LayerSpace
|
|
{
|
|
/// <summary>Drawn through the active camera's transform.</summary>
|
|
World,
|
|
|
|
/// <summary>Drawn in screen coordinates, ignoring the camera (HUD, UI). Never culled.</summary>
|
|
Screen,
|
|
}
|
|
|
|
/// <summary>How sprites are ordered within a layer.</summary>
|
|
public enum LayerSortMode
|
|
{
|
|
/// <summary>Order by the sprite's <see cref="Sprite.Depth"/> value (smaller = drawn first).</summary>
|
|
Depth,
|
|
|
|
/// <summary>
|
|
/// Order by the entity's world Y position (<see cref="Transform2D.Position"/>):
|
|
/// top-down games, lower on screen = drawn in front. Place sprite origins at the
|
|
/// feet/base so the sort point matches the visual anchor.
|
|
/// </summary>
|
|
YSort,
|
|
}
|
|
|
|
/// <summary>A registered render layer.</summary>
|
|
public sealed record RenderLayer(LayerId Id, string Name, LayerSpace Space, LayerSortMode SortMode);
|
|
|
|
/// <summary>
|
|
/// Registry of render layers. Layers are registered up front (typically when the renderer is
|
|
/// created) and drawn in registration order. Maximum 256 layers. Reads are lock-free and
|
|
/// thread-safe (the renderer reads layers from parallel submit workers); registration swaps
|
|
/// an immutable snapshot, so registering mid-frame never tears a concurrent read.
|
|
/// </summary>
|
|
public sealed class LayerRegistry
|
|
{
|
|
private readonly object _sync = new();
|
|
private volatile RenderLayer[] _layers = [];
|
|
|
|
/// <summary>Creates a registry containing the built-in "Default" world layer.</summary>
|
|
public LayerRegistry() => Register("Default");
|
|
|
|
/// <summary>Number of registered layers.</summary>
|
|
public int Count => _layers.Length;
|
|
|
|
/// <summary>Registers a layer drawn after all previously registered ones.</summary>
|
|
public LayerId Register(
|
|
string name,
|
|
LayerSpace space = LayerSpace.World,
|
|
LayerSortMode sortMode = LayerSortMode.Depth
|
|
)
|
|
{
|
|
lock (_sync)
|
|
{
|
|
var layers = _layers;
|
|
if (layers.Length == 256)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Maximum number of render layers (256) reached."
|
|
);
|
|
}
|
|
|
|
var id = new LayerId((byte)layers.Length);
|
|
var grown = new RenderLayer[layers.Length + 1];
|
|
Array.Copy(layers, grown, layers.Length);
|
|
grown[layers.Length] = new RenderLayer(id, name, space, sortMode);
|
|
_layers = grown;
|
|
return id;
|
|
}
|
|
}
|
|
|
|
/// <summary>Returns the layer with the given id; throws when the id was never registered.</summary>
|
|
public RenderLayer this[LayerId id]
|
|
{
|
|
get
|
|
{
|
|
var layers = _layers;
|
|
return id.Value < layers.Length
|
|
? layers[id.Value]
|
|
: throw new ArgumentOutOfRangeException(
|
|
nameof(id),
|
|
$"Render layer {id.Value} is not registered (registered: {layers.Length})."
|
|
);
|
|
}
|
|
}
|
|
}
|