# lifetime_tracker.luau Coverage

## Total Execution Hits: 1

## Function Coverage Overview: 9.09%

## Function Coverage:

Function | Hits
--- | ---
| 1
print_centered_entity:12 | 0
name:26 | 0
pad:30 | 0
lifetime_tracker_add:36 | 0
<anonymous>:48 | 0
<anonymous>:62 | 0
<anonymous>:89 | 0
<anonymous>:135 | 0
<anonymous>:164 | 0
<anonymous>:175 | 0

## Source Code:

Line | Hits | Code
--- | --- | ---
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