Editor API
Namespace GodotECS.Editor, plus the plugin entry point (global class). Editor tooling: debugger dock, entity inspector backend, AABB gizmos, and the bake button. See Editor tools for workflows.
plugin
[Tool]
public partial class plugin : EditorPlugin
{
public override void _EnterTree();
public override void _ExitTree();
}Compiled only with TOOLS. _EnterTree() adds an EntityDebuggerDock { Name = "ECS Debugger" } to DockSlot.LeftUr and wires the companion tools as children: a BakeTool (visible Bake section inside the dock), an EntityInspector (non-visual mapping backend) and an EcsGizmos node parented to a real 3D host — the editor 3D viewport first, then the edited scene root, then the dock as inert fallback (a Node3D under a Control would never render). _ExitTree() removes the dock and frees the gizmos. Registered via addons/godotecs/plugin.cfg (name="GodotECS", version="0.2.0", script="plugin.cs").
EntityDebuggerDock
public partial class EntityDebuggerDock : Control
{
public const int TimingCapacity = 120; // frame-timing ring buffer
public const double HashInterval = 2.0; // seconds between throttled hash refreshes
public bool Paused { get; private set; }
public bool StepPending { get; }
public World CurrentWorld { get; private set; }
public int TimingCount { get; }
public int SnapshotSaveCount { get; }
public int SystemTimingCount { get; }
public EntityDebuggerDock();
public void RegisterTiming(float ms);
public void ClearTimings();
public float AverageMs();
public float[] GetTimingsOrdered();
public void RegisterSystemTiming(string systemName, float ms); // EMA 0.9·prev + 0.1·ms
public void ClearSystemTimings();
public System.Collections.Generic.List<(string Name, float Ms)> TopSystems(int max = 20);
public void SetPaused(bool paused);
public void TogglePause();
public void RequestStep();
public bool ConsumeStep();
public World[] GetWorlds();
public void SelectWorld(int index);
public static ulong ComputeHash(World w);
public void RefreshNow(); // includes hash
public void RefreshNow(bool includeHash);
public void RefreshHash();
public override void _Ready();
public override void _Process(double delta);
}TimingCapacity— ring-buffer size (120 samples).- Constructor sets
CurrentWorld = World.Default. RegisterTiming(float ms)— pushes a frame sample; negative/NaN/infinite values are stored as0. Redraws the graph when in the tree.ClearTimings()— empties the buffer.AverageMs()— mean over stored samples (0when empty).GetTimingsOrdered()— oldest-first copy of the stored samples.RegisterSystemTiming(name, ms)— feeds the per-system EMA (0.9·prev + 0.1·ms); invalid input (empty name, NaN/inf/negative) is ignored.TopSystems(max = 20)returns the systems sorted by descending average ms — the dock shows the top-20 list in its own label.ClearSystemTimings()empties the table.SetPaused(bool)— setsPausedand updates the pause button label ("Riprendi"/"Pausa").TogglePause()flips it.RequestStep()— arms the one-shot step flag (StepPending);ConsumeStep()returnsfalsewhen disarmed, else disarms and returnstrue.GetWorlds()—{ World.Default, World.Preview }.SelectWorld(int index)— switchesCurrentWorld(out-of-range ignored, keeps selection) and refreshes.ComputeHash(World w)—Snapshot.Hash(w).- Hashing is throttled (
Snapshot.Savecosts a hitch): the periodic refresh every 0.5 s callsRefreshNow(includeHash: false), and a dedicated timer refreshes the hash everyHashInterval(2 s) viaRefreshHash(), which incrementsSnapshotSaveCountand updates the hash label (hash: <X16> bytes: <n>, orerr <Exception>). The manual refresh (button /RefreshNow()) includes the hash.RefreshHashis safe to call without UI. _Ready()— builds the UI (world selector, info label, per-system top label, graph, pause/step/refresh buttons) and refreshes._Process(double delta)— recordsdelta * 1000ms when unpaused and refreshes every 0.5 s.
TimingGraph
Nested inside EntityDebuggerDock (sealed partial class TimingGraph : Control, constructed with its owning dock). Custom _Draw(): dark background rect, one green bar per stored sample scaled to 1.2 × max, and a yellow horizontal line at the average. No public members beyond the constructor.
EntityInspector
public partial class EntityInspector : Node
{
public void RegisterMapping(int multimeshInstanceId, int instanceIndex, Entity entity);
public bool TryGetEntity(int multimeshInstanceId, int instanceIndex, out Entity entity);
public bool UnregisterMapping(int multimeshInstanceId, int instanceIndex);
public void ClearMappings();
public int MappingCount { get; }
public Godot.Collections.Dictionary Inspect(World world, Entity entity);
public Godot.Collections.Dictionary InspectWith<T>(World world, Entity entity)
where T : struct, IComponentData;
}- Mapping key packs both ids:
(long)(uint)multimeshInstanceId << 32 | (uint)instanceIndex.TryGetEntityreturnsfalseon miss;UnregisterMappingreturns whether an entry was removed. Inspect(World world, Entity entity)—Dictionarywithworld(name),tick(int),index,version,alive,liveCount. Null world →World.Default.InspectWith<T>(World world, Entity entity)—Inspectplushas_<TypeName>(bool) and, when present,data_<TypeName>(ToString()of the component orerr:<Exception>).
EcsGizmos
public partial class EcsGizmos : Node3D
{
public bool ShowGizmos { get; set; }
public int BoxCount { get; }
public override void _Ready();
public void AddBox(in GodotECS.Math.Aabb box);
public void SetBoxes(IEnumerable<GodotECS.Math.Aabb> boxes);
public void ClearBoxes();
public override void _Process(double delta);
public void Rebuild();
}ShowGizmos— master toggle, defaulttrue.BoxCount— queued box count.AddBoxappends,SetBoxesreplaces (null → empty),ClearBoxesempties; all mark the mesh dirty.Rebuild()— recreates the internalImmediateMesh/MeshInstance3Dnodes if missing, clears surfaces, and emits 12 line edges per box (unshaded green, shadow-casting off). No-op other than clearing the dirty flag when empty or hidden._Process(double)— hides the mesh node whenShowGizmosisfalseor the node is off-tree; otherwise shows it and rebuilds only when dirty.
BakeTool
public partial class BakeTool : VBoxContainer
{
public string LastStatus { get; private set; } // "idle" | "no-root" | "baked-subtree:<n>" | "bake-error"
public int LastBakedCount { get; private set; }
public override void _Ready();
public bool TryBake();
public bool TryBake(Node root);
public static void ClearPreview();
}LastStatus— starts"idle"; mirrored to the status label.LastBakedCount— entity count of the last successful bake.TryBake(Node root)— clearsWorld.Preview(seeClearPreview), then callsBaker.BakeSubtreeInto(World.Preview, root)directly — no reflection. Success →baked-subtree:<n>withLastBakedCount = n; exception →PushWarning+bake-error+false.TryBake()(no argument) returnsfalsewith statusno-root: baking requires an explicit subtree.ClearPreview()— static; emptiesWorld.PreviewviaDestroyBatch(and its orphaned dynamic buffers), so consecutive bakes never accumulate leftovers.- The old reflection layer (
BakeAll/Bake/BakeWorld/BakeActiveSceneprobing, statusesbaker-missing/baker-no-entrypoint/baked:<name>/bake-failed:<name>) was removed in v0.2 — the authoring assembly is a hard dependency now.