Skip to content

Editor tools

GodotECS ships editor tooling under GodotECS.Editor plus the plugin entry point. Everything is built in code (no .tscn dependencies), and the expensive logic is kept UI-free so it can be unit-tested without the scene tree.

Plugin setup

The addon entry point is plugin (addons/godotecs/plugin.cs, a [Tool] EditorPlugin compiled only with TOOLS):

  • _EnterTree() creates an EntityDebuggerDock named "ECS Debugger" and adds it to DockSlot.LeftUr.
  • _ExitTree() removes and frees it.

Enable it via Project → Project Settings → Plugins → GodotECS → Enable. plugin.cfg declares name GodotECS, version 0.1.0, script plugin.cs.

Debugger dock

EntityDebuggerDock (Control) is the main ECS panel. UI, built in _Ready():

  • World selector (OptionButton: Default / Preview) — switches CurrentWorld.
  • Info label — World: <name> Tick: <tick> Live: <live> Query(All): <count>.
  • Hash label — hash: <X16> bytes: <n> from a world snapshot (Snapshot.Save + Snapshot.HashBytes), or hash: err <Exception> on failure. Comparing hashes across runs is the quick determinism check.
  • Timing label — avg: <ms> ms over <n> campioni, with a [PAUSA] marker when paused.
  • TimingGraph — an embedded bar chart of the last up-to-120 frame times with an average line.
  • Buttons: Pausa/Riprendi, Step, Refresh.

Workflow logic (all callable without the tree):

  • Pause/step: SetPaused(bool) / TogglePause() freeze the timing feed; RequestStep() sets a one-shot flag consumed via ConsumeStep() — advance exactly one tick while paused.
  • Timings: RegisterTiming(float ms) pushes into a 120-sample ring buffer (TimingCapacity), sanitizing negative/NaN/infinity to 0. AverageMs(), GetTimingsOrdered() (oldest-first copy), TimingCount, ClearTimings().
  • Worlds: GetWorlds() returns { World.Default, World.Preview }; SelectWorld(index) switches (out-of-range ignored) and refreshes.
  • ComputeHash(World) is Snapshot.Hash(w).
  • _Process records delta * 1000 as a timing sample when unpaused and refreshes the labels every 0.5 s (RefreshInterval).

Typical session: open the dock, watch live count and average ms while playing, pause + step to inspect a spike, compare snapshot hashes between two runs to confirm determinism.

Inspector

EntityInspector (Node, non-visual) answers "what is this rendered thing?" and "what is on this entity?":

  • MultiMesh → entity mapping: the render sync registers each drawn instance via RegisterMapping(multimeshInstanceId, instanceIndex, entity). Clicking a rendered instance raycasts to its MultiMesh + index, and TryGetEntity(...) resolves the Entity. UnregisterMapping(...) removes one entry, ClearMappings() all, MappingCount reports the size.
  • Inspect(World, Entity) returns a Dictionary with world, tick, index, version, alive, liveCount (null world → World.Default).
  • InspectWith<T>(World, Entity) adds has_<TypeName> plus data_<TypeName> (ToString() of the component, or err:<Exception> on failure) for any struct : IComponentData.

Design note: this is the inspection backend, not the Godot property inspector — the dock or a custom inspector panel calls it and renders the dictionary.

Gizmos

EcsGizmos (Node3D) draws world-space AABBs (GodotECS.Math.Aabb) as green wireframe lines through an ImmediateMesh (unshaded, no shadows):

  • AddBox(in Aabb) appends one box; SetBoxes(IEnumerable<Aabb>) replaces the set (null clears); ClearBoxes() empties it. Every mutation marks the mesh dirty.
  • Rebuild() redraws all 12 edges per box immediately; _Process rebuilds only when dirty, and hides the mesh node entirely when ShowGizmos is false or the node is not visible in the tree.
  • BoxCount reports the pending box count; ShowGizmos (default true) is the master toggle.

Intended contents: chunk AABBs, broadphase grid cells, or velocity vectors for the current selection — never one gizmo per entity at 100k scale.

Bake tool

BakeTool (VBoxContainer) is a Bake ECS button plus a status label for editor use:

  • TryBake() with no argument looks for a zero-parameter static Bake* entry point (BakeAll, Bake, BakeWorld, BakeActiveScene) on GodotECS.Authoring.Baker via reflection.
  • TryBake(Node root) bakes a subtree: prefers BakeSubtreeInto(World.Preview, root) and reports baked-subtree:<n>, falling back to BakeSubtree(root).
  • LastStatus records the outcome (idle, baked-subtree:<n>, baked:<name>, bake-failed:<name>, baker-missing, baker-no-entrypoint, bake-error) and mirrors it to the label. Failures also PushWarning and return false without modifying any scene.

The reflection lookup exists so the button degrades gracefully (warning + status) when the authoring assembly is absent, instead of breaking the editor.

Full API

Exact signatures for plugin, EntityDebuggerDock (+TimingGraph), EntityInspector, EcsGizmos, and BakeTool: Editor API.

GodotECS docs — version 0.1.0. All rights reserved until a license is added.