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, then wires the companion tools: aBakeTool(the Bake section inside the dock), anEntityInspector(non-visual) as dock children, and anEcsGizmosnode parented to a 3D host — the editor 3D viewport first, else the edited scene root (aNode3Dunder aControlnever renders in the 3D view)._ExitTree()removes and frees everything.
Enable it via Project → Project Settings → Plugins → GodotECS → Enable. plugin.cfg declares name GodotECS, version 0.2.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>. - Per-system label — the top-20 systems by average ms (EMA smoothing:
0.9·prev + 0.1·sample; fed by any system or bridge code callingRegisterSystemTiming(name, ms)). - 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().TopSystems(20)returns the per-system EMA table sorted by ms,ClearSystemTimings()resets it. - Hashing is throttled: the periodic refresh (every 0.5 s) skips
Snapshot.Save; a dedicated timer recomputes the hash everyHashInterval(2 s) viaRefreshHash()(SnapshotSaveCountcounts the saves). The Refresh button /RefreshNow()includes the hash on demand. - 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, average ms and the slowest systems 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, wired directly to GodotECS.Authoring.Baker (no reflection since v0.2):
TryBake(Node root)clearsWorld.Previewand bakes the subtree into it viaBakeSubtreeInto, reportingbaked-subtree:<n>and settingLastBakedCount.TryBake()without a node returnsfalsewith statusno-root— baking requires an explicit subtree.LastStatusrecords the outcome (idle,no-root,baked-subtree:<n>,bake-error) and mirrors it to the label. FailuresPushWarningand returnfalsewithout modifying any scene.BakeTool.ClearPreview()emptiesWorld.Preview(DestroyBatch), so consecutive bakes never accumulate leftover entities.
Full API
Exact signatures for plugin, EntityDebuggerDock (+TimingGraph), EntityInspector, EcsGizmos, and BakeTool: Editor API.