From de8e2638285e18dd75a2de757d2b421d05ec4ccf Mon Sep 17 00:00:00 2001 From: Ukendio Date: Wed, 12 Mar 2025 16:30:19 +0100 Subject: [PATCH] Add entity visualiser --- .luaurc | 4 +- test/devtools_test.luau | 6 ++- tools/ansi.luau | 33 ++++++++++++ tools/entity_visualiser.luau | 50 +++++++++++++++++ tools/lifetime_tracker.luau | 102 ++++++++++++----------------------- {test => tools}/testkit.luau | 0 6 files changed, 123 insertions(+), 72 deletions(-) create mode 100644 tools/ansi.luau create mode 100644 tools/entity_visualiser.luau rename {test => tools}/testkit.luau (100%) diff --git a/.luaurc b/.luaurc index e3c9db3..d1ae244 100644 --- a/.luaurc +++ b/.luaurc @@ -1,9 +1,9 @@ { "aliases": { "jecs": "jecs", - "testkit": "test/testkit", + "testkit": "tools/testkit", "mirror": "mirror", - "tools": "tools" + "tools": "tools", }, "languageMode": "strict" } diff --git a/test/devtools_test.luau b/test/devtools_test.luau index e227c70..90edb98 100644 --- a/test/devtools_test.luau +++ b/test/devtools_test.luau @@ -1,4 +1,6 @@ local jecs = require("@jecs") +local pair = jecs.pair +local ChildOf = jecs.ChildOf local lifetime_tracker_add = require("@tools/lifetime_tracker") local world = lifetime_tracker_add(jecs.world()) world:print_snapshot() @@ -8,13 +10,15 @@ world:delete(e) world:print_snapshot() local e2 = world:entity() +world:add(e2, pair(ChildOf, e1)) local e3 = world:entity() +world:add(e3, pair(ChildOf, e1)) world:print_snapshot() world:delete(e1) world:delete(e2) world:delete(e3) world:print_snapshot() -world:print_entities() +world:print_entity_index() world:entity() world:entity() world:print_snapshot() diff --git a/tools/ansi.luau b/tools/ansi.luau new file mode 100644 index 0000000..e4da53c --- /dev/null +++ b/tools/ansi.luau @@ -0,0 +1,33 @@ +return { + white_underline = function(s: any) + return `\27[1;4m{s}\27[0m` + end, + + white = function(s: any) + return `\27[37;1m{s}\27[0m` + end, + + green = function(s: any) + return `\27[32;1m{s}\27[0m` + end, + + red = function(s: any) + return `\27[31;1m{s}\27[0m` + end, + + yellow = function(s: any) + return `\27[33;1m{s}\27[0m` + end, + + red_highlight = function(s: any) + return `\27[41;1;30m{s}\27[0m` + end, + + green_highlight = function(s: any) + return `\27[42;1;30m{s}\27[0m` + end, + + gray = function(s: any) + return `\27[30;1m{s}\27[0m` + end, +} diff --git a/tools/entity_visualiser.luau b/tools/entity_visualiser.luau new file mode 100644 index 0000000..3797c33 --- /dev/null +++ b/tools/entity_visualiser.luau @@ -0,0 +1,50 @@ +local jecs = require("@jecs") +local ECS_GENERATION = jecs.ECS_GENERATION +local ECS_ID = jecs.ECS_ID +local ansi = require("@tools/ansi") + +local function pe(e: any) + local gen = ECS_GENERATION(e) + return ansi.green(`e{ECS_ID(e)}`)..ansi.yellow(`v{gen}`) +end + +local function name(world: jecs.World, id: any) + return world:get(id, jecs.Name) or `${id}` +end + +local function components(world: jecs.World, entity: any) + local r = jecs.entity_index_try_get(world.entity_index, entity) + if not r then + return false + end + + local archetype = r.archetype + local row = r.row + print(`Entity {pe(entity)}`) + print("-----------------------------------------------------") + for i, column in archetype.columns do + local component = archetype.types[i] + local n + if jecs.IS_PAIR(component) then + n = `({name(world, jecs.pair_first(world, component))}, {name(world, jecs.pair_second(world, component))})` + else + n = name(world, component) + end + local data = column[row] or "TAG" + print(`| {n} | {data} |`) + end + print("-----------------------------------------------------") + return true +end + +local world = jecs.world() +local A = world:component() +world:set(A, jecs.Name, "A") +local e = world:entity() +world:set(e, A, true) +components(world, e) + +return { + components = components, + prettify = pe +} diff --git a/tools/lifetime_tracker.luau b/tools/lifetime_tracker.luau index c80d133..567949e 100644 --- a/tools/lifetime_tracker.luau +++ b/tools/lifetime_tracker.luau @@ -4,70 +4,19 @@ local ECS_ID = jecs.ECS_ID local __ = jecs.Wildcard local pair = jecs.pair -local testkit = require("@testkit") -local BENCH, START = testkit.benchmark() - -local it = testkit.test() -local TEST, CASE = it.TEST, it.CASE -local CHECK, FINISH = it.CHECK, it.FINISH -local SKIP, FOCUS = it.SKIP, it.FOCUS -local CHECK_EXPECT_ERR = it.CHECK_EXPECT_ERR - - -local c = { - white_underline = function(s: any) - return `\27[1;4m{s}\27[0m` - end, - - white = function(s: any) - return `\27[37;1m{s}\27[0m` - end, - - green = function(s: any) - return `\27[32;1m{s}\27[0m` - end, - - red = function(s: any) - return `\27[31;1m{s}\27[0m` - end, - - yellow = function(s: any) - return `\27[33;1m{s}\27[0m` - end, - - red_highlight = function(s: any) - return `\27[41;1;30m{s}\27[0m` - end, - - green_highlight = function(s: any) - return `\27[42;1;30m{s}\27[0m` - end, - - gray = function(s: any) - return `\27[30;1m{s}\27[0m` - end, -} - -local function pe(e: any) - local gen = ECS_GENERATION(e) - return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`) -end +local pe = require("@tools/entity_visualiser").prettify function print_centered_entity(entity, width: number) local entity_str = tostring(entity) local entity_length = #entity_str - -- Calculate total padding needed to center the string - local padding_total = width - 2 - entity_length -- Subtract 2 for the `| |` characters + local padding_total = width - 2 - entity_length - -- Calculate padding for the left and right local padding_left = math.floor(padding_total / 2) local padding_right = padding_total - padding_left - -- Build the centered string local centered_str = string.rep(" ", padding_left) .. entity_str .. string.rep(" ", padding_right) - -- Print with pipes around the centered string print("|" .. centered_str .. "|") end @@ -82,36 +31,51 @@ local function lifetime_tracker_add(world: jecs.World) local w = setmetatable({}, { __index = world }) w.delete = function(self, e) - print("Entity deleted:", e) - - for child in world:each(pair(__, e)) do + print(`*deleting {pe(e)}`) + local idr_t = component_index[pair(__, e)] + if idr_t then + print(`{pe(e)} has the following dependencies:`) + for archetype_id in idr_t.cache do + local archetype = world.archetypes[archetype_id] + local entities = {} + for _, dependency in archetype.entities do + print(` {pe(dependency)}`) + end + end end - return world_delete(world, e) + + world_delete(world, e) + print(`*deleted {pe(e)}`) end w.entity = function(self) local e = world_entity(world) - print("Entity created:", pe(e)) + print(`*created {pe(e)}`) return e end - w.print_entities = function(self) + w.print_entity_index = function(self) local max_id = entity_index.max_id local alive_count = entity_index.alive_count local alive = table.move(dense_array, 1+jecs.Rest::any, alive_count, 1, {}) local dead = table.move(dense_array, alive_count + 1, max_id, 1, {}) local sep = "|--------|" - print("|-alive--|") - for i = 1, #alive do - local e = pe(alive[i]) - print_centered_entity(e, 32) - print(sep) + if #alive > 0 then + print("|-alive--|") + for i = 1, #alive do + local e = pe(alive[i]) + print_centered_entity(e, 32) + print(sep) + end + print("\n") end - print("\n") - print("|--dead--|") - for i = 1, #dead do - print_centered_entity(pe(dead[i]), 32) - print(sep) + + if #dead > 0 then + print("|--dead--|") + for i = 1, #dead do + print_centered_entity(pe(dead[i]), 32) + print(sep) + end end end local timelines = {} diff --git a/test/testkit.luau b/tools/testkit.luau similarity index 100% rename from test/testkit.luau rename to tools/testkit.luau