1 | 1 | local jecs = require("@jecs") |
2 | 1 | local ECS_GENERATION = jecs.ECS_GENERATION |
3 | 1 | local ECS_ID = jecs.ECS_ID |
4 | 1 | local __ = jecs.Wildcard |
5 | 1 | local pair = jecs.pair |
6 | N/A | | >
7 | 1 | local prettify = require("@tools/entity_visualiser").prettify |
8 | N/A | | >
9 | 1 | local pe = prettify |
10 | 1 | local ansi = require("@tools/ansi") |
11 | N/A | | >
12 | 1 | function print_centered_entity(entity, width: number) |
13 | 0 | local entity_str = tostring(entity) |
14 | 0 | local entity_length = #entity_str |
15 | N/A | | >
16 | 0 | local padding_total = width - 2 - entity_length |
17 | N/A | | >
18 | 0 | local padding_left = math.floor(padding_total / 2) |
19 | 0 | local padding_right = padding_total - padding_left |
20 | N/A | | >
21 | 0 | local centered_str = string.rep(" ", padding_left) .. entity_str .. string.rep(" ", padding_right) |
22 | N/A | | >
23 | 0 | print("|" .. centered_str .. "|") |
24 | N/A | end | >
25 | N/A | | >
26 | 1 | local function name(world, e) |
27 | 0 | return world:get(world, e, jecs.Name) or pe(e) |
28 | N/A | end | >
29 | 1 | local padding_enabled = false |
30 | 1 | local function pad() |
31 | 0 | if padding_enabled then |
32 | 0 | print("") |
33 | N/A | end | >
34 | N/A | end | >
35 | N/A | | >
36 | 1 | local function lifetime_tracker_add(world: jecs.World, opt) |
37 | 0 | local entity_index = world.entity_index |
38 | 0 | local dense_array = entity_index.dense_array |
39 | 0 | local component_index = world.component_index |
40 | N/A | | >
41 | 0 | local ENTITY_RANGE = (jecs.Rest :: any) + 1 |
42 | N/A | | >
43 | 0 | local w = setmetatable({}, { __index = world }) |
44 | N/A | | >
45 | 0 | padding_enabled = opt.padding_enabled |
46 | N/A | | >
47 | 0 | local world_entity = world.entity |
48 | 0 | w.entity = function(self, entity) |
49 | 0 | if entity then |
50 | 0 | return world_entity(world, entity) |
51 | N/A | end | >
52 | 0 | local will_recycle = entity_index.max_id ~= entity_index.alive_count |
53 | 0 | local e = world_entity(world) |
54 | 0 | if will_recycle then |
55 | 0 | print(`*recycled {pe(e)}`) |
56 | 0 | else |
57 | 0 | print(`*created {pe(e)}`) |
58 | N/A | end | >
59 | 0 | pad() |
60 | 0 | return e |
61 | N/A | end | >
62 | 0 | w.print_entity_index = function(self) |
63 | 0 | local max_id = entity_index.max_id |
64 | 0 | local alive_count = entity_index.alive_count |
65 | 0 | local alive = table.move(dense_array, 1 + jecs.Rest :: any, alive_count, 1, {}) |
66 | 0 | local dead = table.move(dense_array, alive_count + 1, max_id, 1, {}) |
67 | N/A | | >
68 | 0 | local sep = "|--------|" |
69 | 0 | if #alive > 0 then |
70 | 0 | print("|-alive--|") |
71 | 0 | for i = 1, #alive do |
72 | 0 | local e = pe(alive[i]) |
73 | 0 | print_centered_entity(e, 32) |
74 | 0 | print(sep) |
75 | N/A | end | >
76 | 0 | print("\n") |
77 | N/A | end | >
78 | N/A | | >
79 | 0 | if #dead > 0 then |
80 | 0 | print("|--dead--|") |
81 | 0 | for i = 1, #dead do |
82 | 0 | print_centered_entity(pe(dead[i]), 32) |
83 | 0 | print(sep) |
84 | N/A | end | >
85 | N/A | end | >
86 | 0 | pad() |
87 | N/A | end | >
88 | 0 | local timelines = {} |
89 | 0 | w.print_snapshot = function(self) |
90 | 0 | local timeline = #timelines + 1 |
91 | 0 | local entity_column_width = 10 |
92 | 0 | local status_column_width = 8 |
93 | N/A | | >
94 | 0 | local header = string.format("| %-" .. entity_column_width .. "s |", "Entity") |
95 | 0 | for i = 1, timeline do |
96 | 0 | header = header .. string.format(" %-" .. status_column_width .. "s |", string.format("T%d", i)) |
97 | N/A | end | >
98 | N/A | | >
99 | 0 | local max_id = entity_index.max_id |
100 | 0 | local alive_count = entity_index.alive_count |
101 | 0 | local alive = table.move(dense_array, 1 + jecs.Rest :: any, alive_count, 1, {}) |
102 | 0 | local dead = table.move(dense_array, alive_count + 1, max_id, 1, {}) |
103 | N/A | | >
104 | 0 | local data = {} |
105 | 0 | print("-------------------------------------------------------------------") |
106 | 0 | print(header) |
107 | N/A | | >
108 | N/A | -- Store the snapshot data for this timeline | >
109 | 0 | for i = ENTITY_RANGE, max_id do |
110 | 0 | if dense_array[i] then |
111 | 0 | local entity = dense_array[i] |
112 | 0 | local id = ECS_ID(entity) |
113 | 0 | local status = "alive" |
114 | 0 | if not world:contains(entity) then |
115 | 0 | status = "dead" |
116 | N/A | end | >
117 | 0 | data[id] = status |
118 | N/A | end | >
119 | N/A | end | >
120 | N/A | | >
121 | 0 | table.insert(timelines, data) |
122 | N/A | | >
123 | N/A | -- Create a table to hold entity data for sorting | >
124 | 0 | local entities = {} |
125 | 0 | for i = ENTITY_RANGE, max_id do |
126 | 0 | if dense_array[i] then |
127 | 0 | local entity = dense_array[i] |
128 | 0 | local id = ECS_ID(entity) |
129 | N/A | -- Push entity and id into the new `entities` table | >
130 | 0 | table.insert(entities, { entity = entity, id = id }) |
131 | N/A | end | >
132 | N/A | end | >
133 | N/A | | >
134 | N/A | -- Sort the entities by ECS_ID | >
135 | 0 | table.sort(entities, function(a, b) |
136 | 0 | return a.id < b.id |
137 | N/A | end) | >
138 | N/A | | >
139 | N/A | -- Print the sorted rows | >
140 | 0 | for _, entity_data in ipairs(entities) do |
141 | 0 | local entity = entity_data.entity |
142 | 0 | local id = entity_data.id |
143 | 0 | local status = "alive" |
144 | 0 | if id > alive_count then |
145 | 0 | status = "dead" |
146 | N/A | end | >
147 | 0 | local row = string.format("| %-" .. entity_column_width .. "s |", pe(entity)) |
148 | 0 | for j = 1, timeline do |
149 | 0 | local timeline_data = timelines[j] |
150 | 0 | local entity_data = timeline_data[id] |
151 | 0 | if entity_data then |
152 | 0 | row = row .. string.format(" %-" .. status_column_width .. "s |", entity_data) |
153 | 0 | else |
154 | 0 | row = row .. string.format(" %-" .. status_column_width .. "s |", "-") |
155 | N/A | end | >
156 | N/A | end | >
157 | 0 | print(row) |
158 | N/A | end | >
159 | 0 | print("-------------------------------------------------------------------") |
160 | 0 | pad() |
161 | N/A | end | >
162 | 0 | local world_add = world.add |
163 | 0 | local relations = {} |
164 | 0 | w.add = function(self, entity: any, component: any) |
165 | 0 | world_add(world, entity, component) |
166 | 0 | if jecs.IS_PAIR(component) then |
167 | 0 | local relation = jecs.pair_first(world, component) |
168 | 0 | local target = jecs.pair_second(world, component) |
169 | 0 | print(`*added ({pe(relation)}, {pe(target)}) to {pe(entity)}`) |
170 | 0 | pad() |
171 | N/A | end | >
172 | N/A | end | >
173 | N/A | | >
174 | 0 | local world_delete = world.delete |
175 | 0 | w.delete = function(self, e) |
176 | 0 | world_delete(world, e) |
177 | N/A | | >
178 | 0 | local idr_t = component_index[pair(__, e)] |
179 | 0 | if idr_t then |
180 | 0 | for archetype_id in idr_t.cache do |
181 | 0 | local archetype = world.archetypes[archetype_id] |
182 | 0 | for _, id in archetype.types do |
183 | 0 | if not jecs.IS_PAIR(id) then |
184 | 0 | continue |
185 | N/A | end | >
186 | 0 | local object = jecs.pair_second(world, id) |
187 | 0 | if object ~= e then |
188 | 0 | continue |
189 | N/A | end | >
190 | 0 | local id_record = component_index[id] |
191 | 0 | local flags = id_record.flags |
192 | 0 | local flags_delete_mask: number = bit32.band(flags, jecs.ECS_ID_DELETE) |
193 | 0 | if flags_delete_mask ~= 0 then |
194 | 0 | for _, entity in archetype.entities do |
195 | 0 | print(`*deleted dependant {pe(entity)} of {pe(e)}`) |
196 | 0 | pad() |
197 | N/A | end | >
198 | 0 | break |
199 | 0 | else |
200 | 0 | for _, entity in archetype.entities do |
201 | 0 | print( |
202 | 0 | `*removed dependency ({pe(jecs.pair_first(world, id))}, {pe(object)}) from {pe(entity)}` |
203 | 0 | ) |
204 | N/A | end | >
205 | N/A | end | >
206 | N/A | end | >
207 | N/A | end | >
208 | N/A | end | >
209 | N/A | | >
210 | 0 | print(`*deleted {pe(e)}`) |
211 | 0 | pad() |
212 | N/A | end | >
213 | 0 | return w |
214 | N/A | end | >
215 | N/A | | >
216 | 1 | return lifetime_tracker_add |