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 anEntityDebuggerDocknamed"ECS Debugger"and adds it toDockSlot.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) — switchesCurrentWorld. - 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), orhash: 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 viaConsumeStep()— advance exactly one tick while paused. - Timings:
RegisterTiming(float ms)pushes into a 120-sample ring buffer (TimingCapacity), sanitizing negative/NaN/infinity to0.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)isSnapshot.Hash(w)._Processrecordsdelta * 1000as 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, andTryGetEntity(...)resolves theEntity.UnregisterMapping(...)removes one entry,ClearMappings()all,MappingCountreports the size. Inspect(World, Entity)returns aDictionarywithworld,tick,index,version,alive,liveCount(null world →World.Default).InspectWith<T>(World, Entity)addshas_<TypeName>plusdata_<TypeName>(ToString()of the component, orerr:<Exception>on failure) for anystruct : 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;_Processrebuilds only when dirty, and hides the mesh node entirely whenShowGizmosisfalseor the node is not visible in the tree.BoxCountreports the pending box count;ShowGizmos(defaulttrue) 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 staticBake*entry point (BakeAll,Bake,BakeWorld,BakeActiveScene) onGodotECS.Authoring.Bakervia reflection.TryBake(Node root)bakes a subtree: prefersBakeSubtreeInto(World.Preview, root)and reportsbaked-subtree:<n>, falling back toBakeSubtree(root).LastStatusrecords 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 alsoPushWarningand returnfalsewithout 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.