Files
Leonid PershinandClaude Opus 4.8 09dbdfad79
CI / build-test (push) Successful in 1m14s
Add MrGameEng.Inspector: in-engine ECS debugger overlay
Introduce a Chrome-DevTools-style ECS inspector in the UI library. The
headless core (ComponentReflector + EcsInspector) enumerates the store's
entities grouped by archetype, reflects a selected entity's components and
fields, and writes simple scalar edits (number/bool/enum) back through a
generic AddComponent — all unit-tested without a GPU. The Myra overlay
(EcsInspectorUi + InspectorSystems) adds a side panel with an
archetypes -> entities -> fields tree, an editable field view, a
world-pick mode with a selection highlight, and a renderer performance
tab; wired via scene.UseInspector(renderer), toggled with F1.

The UI library now references Graphics (for picking, renderer timings and
Transform2D/Sprite). Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 12:45:50 +03:00

145 lines
3.8 KiB
C#

using Friflo.Engine.ECS;
using MrGameEng.Inspector;
using Xunit;
namespace MrGameEng.Inspector.Tests;
public class EcsInspectorTests
{
private enum Mode
{
Idle,
Run,
}
private struct Probe : IComponent
{
public int Count;
public bool Active;
public Mode State;
}
private struct Marker : IComponent
{
public int Value;
}
[Fact]
public void Archetypes_GroupsEntitiesByComponentSet()
{
var store = new EntityStore();
store.CreateEntity(new Probe { Count = 1 });
store.CreateEntity(new Probe { Count = 2 });
store.CreateEntity(new Marker { Value = 9 });
var inspector = new EcsInspector(store);
var rows = inspector.Archetypes();
var probeRow = Single(rows, r => r.Signature.Contains("Probe"));
Assert.Equal(2, probeRow.Count);
Assert.Contains(rows, r => r.Signature.Contains("Marker") && r.Count == 1);
}
[Fact]
public void EntitiesOf_ReturnsIdsOfArchetype()
{
var store = new EntityStore();
var a = store.CreateEntity(new Probe { Count = 1 });
var b = store.CreateEntity(new Probe { Count = 2 });
var inspector = new EcsInspector(store);
var probeRow = Single(inspector.Archetypes(), r => r.Signature.Contains("Probe"));
var ids = inspector.EntitiesOf(probeRow.Index);
Assert.Contains(a.Id, ids);
Assert.Contains(b.Id, ids);
Assert.Equal(2, ids.Count);
}
[Fact]
public void Inspect_ReturnsComponentFields()
{
var store = new EntityStore();
var entity = store.CreateEntity(
new Probe
{
Count = 5,
Active = true,
State = Mode.Run,
}
);
var inspector = new EcsInspector(store);
var view = Single(inspector.Inspect(entity.Id), c => c.TypeName == "Probe");
Assert.Equal("5", Field(view, "Count").Value);
Assert.Equal("true", Field(view, "Active").Value);
Assert.Equal("Run", Field(view, "State").Value);
}
[Fact]
public void SetField_WritesValueBackToEntity()
{
var store = new EntityStore();
var entity = store.CreateEntity(new Probe { Count = 5 });
var inspector = new EcsInspector(store);
var ok = inspector.SetField(entity.Id, typeof(Probe), "Count", "42");
Assert.True(ok);
Assert.Equal(42, entity.GetComponent<Probe>().Count);
}
[Fact]
public void SetField_RejectsInvalidValue()
{
var store = new EntityStore();
var entity = store.CreateEntity(new Probe { Count = 5 });
var inspector = new EcsInspector(store);
Assert.False(inspector.SetField(entity.Id, typeof(Probe), "Count", "bad"));
Assert.Equal(5, entity.GetComponent<Probe>().Count);
}
[Fact]
public void Search_FiltersArchetypesBySignature()
{
var store = new EntityStore();
store.CreateEntity(new Probe());
store.CreateEntity(new Marker());
var inspector = new EcsInspector(store);
inspector.SetSearch("Marker");
var rows = inspector.Archetypes();
Assert.Single(rows);
Assert.Contains("Marker", rows[0].Signature);
}
private static T Single<T>(IReadOnlyList<T> items, Func<T, bool> predicate)
{
foreach (var item in items)
{
if (predicate(item))
{
return item;
}
}
throw new Xunit.Sdk.XunitException("no matching item");
}
private static FieldRow Field(ComponentView view, string name)
{
foreach (var row in view.Fields)
{
if (row.Name == name)
{
return row;
}
}
throw new Xunit.Sdk.XunitException($"field {name} not found");
}
}