mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-04 10:59:18 +00:00 
			
		
		
		
	Devtools initial commit
This commit is contained in:
		
							parent
							
								
									18019679d5
								
							
						
					
					
						commit
						d15266b6d5
					
				
					 6 changed files with 308 additions and 23 deletions
				
			
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -65,3 +65,7 @@ drafts/
 | 
			
		|||
 | 
			
		||||
# Luau tools
 | 
			
		||||
profile.*
 | 
			
		||||
 | 
			
		||||
# Patch files
 | 
			
		||||
 | 
			
		||||
*.patch
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								.luaurc
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								.luaurc
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -2,7 +2,8 @@
 | 
			
		|||
    "aliases": {
 | 
			
		||||
        "jecs": "jecs",
 | 
			
		||||
        "testkit": "test/testkit",
 | 
			
		||||
        "mirror": "mirror"
 | 
			
		||||
        "mirror": "mirror",
 | 
			
		||||
        "tools": "tools"
 | 
			
		||||
    },
 | 
			
		||||
    "languageMode": "strict"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,41 +5,60 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
			
		|||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
 | 
			
		||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
 | 
			
		||||
local jecs = require(ReplicatedStorage.Lib)
 | 
			
		||||
local pair = jecs.pair
 | 
			
		||||
local newWorld = Matter.World.new()
 | 
			
		||||
local ecs = jecs.World.new()
 | 
			
		||||
local mirror = require(ReplicatedStorage.mirror)
 | 
			
		||||
local mcs = mirror.World.new()
 | 
			
		||||
 | 
			
		||||
local A, B = Matter.component(), Matter.component()
 | 
			
		||||
local C, D = ecs:component(), ecs:component()
 | 
			
		||||
local C1 = ecs:component()
 | 
			
		||||
local C2 = ecs:entity()
 | 
			
		||||
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
			
		||||
local C3 = ecs:entity()
 | 
			
		||||
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
			
		||||
local C4 = ecs:entity()
 | 
			
		||||
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
			
		||||
local E1 = mcs:component()
 | 
			
		||||
local E2 = mcs:entity()
 | 
			
		||||
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
			
		||||
local E3 = mcs:entity()
 | 
			
		||||
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
			
		||||
local E4 = mcs:entity()
 | 
			
		||||
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
			
		||||
 | 
			
		||||
local registry2 = ecr.registry()
 | 
			
		||||
 | 
			
		||||
return {
 | 
			
		||||
	ParameterGenerator = function()
 | 
			
		||||
		local matter_entities = {}
 | 
			
		||||
		local jecs_entities = {}
 | 
			
		||||
		local entities = {
 | 
			
		||||
			matter = matter_entities,
 | 
			
		||||
			jecs = jecs_entities,
 | 
			
		||||
		}
 | 
			
		||||
		local j = ecs:entity()
 | 
			
		||||
		ecs:set(j, C1, true)
 | 
			
		||||
		local m = mcs:entity()
 | 
			
		||||
		mcs:set(m, E1, true)
 | 
			
		||||
		for i = 1, 1000 do
 | 
			
		||||
			table.insert(matter_entities, newWorld:spawn(A(), B()))
 | 
			
		||||
			local e = ecs:entity()
 | 
			
		||||
			ecs:set(e, C, {})
 | 
			
		||||
			ecs:set(e, D, {})
 | 
			
		||||
			table.insert(jecs_entities, e)
 | 
			
		||||
			local friend1 = ecs:entity()
 | 
			
		||||
			local friend2 = mcs:entity()
 | 
			
		||||
 | 
			
		||||
			ecs:add(friend1, pair(C2, j))
 | 
			
		||||
			ecs:add(friend1, pair(C3, j))
 | 
			
		||||
			ecs:add(friend1, pair(C4, j))
 | 
			
		||||
 | 
			
		||||
			mcs:add(friend2, pair(E2, m))
 | 
			
		||||
			mcs:add(friend2, pair(E3, m))
 | 
			
		||||
			mcs:add(friend2, pair(E4, m))
 | 
			
		||||
		end
 | 
			
		||||
		return entities
 | 
			
		||||
		return {
 | 
			
		||||
			m = m,
 | 
			
		||||
			j = j,
 | 
			
		||||
		}
 | 
			
		||||
	end,
 | 
			
		||||
 | 
			
		||||
	Functions = {
 | 
			
		||||
		Matter = function(_, entities)
 | 
			
		||||
			for _, entity in entities.matter do
 | 
			
		||||
				newWorld:despawn(entity)
 | 
			
		||||
			end
 | 
			
		||||
		Mirror = function(_, a)
 | 
			
		||||
			mcs:delete(a.m)
 | 
			
		||||
		end,
 | 
			
		||||
 | 
			
		||||
		Jecs = function(_, entities)
 | 
			
		||||
			for _, entity in entities.jecs do
 | 
			
		||||
				ecs:delete(entity)
 | 
			
		||||
			end
 | 
			
		||||
		Jecs = function(_, a)
 | 
			
		||||
			ecs:delete(a.j)
 | 
			
		||||
		end,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										48
									
								
								demo/src/ReplicatedStorage/track.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								demo/src/ReplicatedStorage/track.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
local events = {}
 | 
			
		||||
 | 
			
		||||
local function trackers_invoke(event, component, entity, ...)
 | 
			
		||||
	local trackers = events[event][component]
 | 
			
		||||
	if not trackers then
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	for _, tracker in trackers do
 | 
			
		||||
		tracker(entity, data)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function trackers_init(event, component, fn)
 | 
			
		||||
	local ob = events[event]
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		connect = function(component, fn)
 | 
			
		||||
			local trackers = ob[component]
 | 
			
		||||
			if not trackers then
 | 
			
		||||
				trackers = {}
 | 
			
		||||
				ob[component] = trackers
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			table.insert(trackers, fn)
 | 
			
		||||
		end,
 | 
			
		||||
		invoke = function(component, ...)
 | 
			
		||||
			trackers_invoke(event, component, ...)
 | 
			
		||||
		end
 | 
			
		||||
	}
 | 
			
		||||
	return function(component, fn)
 | 
			
		||||
		local trackers = ob[component]
 | 
			
		||||
		if not trackers then
 | 
			
		||||
			trackers = {}
 | 
			
		||||
			ob[component] = trackers
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		table.insert(trackers, fn)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local trackers = {
 | 
			
		||||
	emplace = trackers_init("emplace"),
 | 
			
		||||
	add = trackers_init("added"),
 | 
			
		||||
	remove = trackers_init("removed")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
return trackers
 | 
			
		||||
							
								
								
									
										20
									
								
								test/devtools_test.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								test/devtools_test.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
local jecs = require("@jecs")
 | 
			
		||||
local lifetime_tracker_add = require("@tools/lifetime_tracker")
 | 
			
		||||
local world = lifetime_tracker_add(jecs.world())
 | 
			
		||||
world:print_snapshot()
 | 
			
		||||
local e = world:entity()
 | 
			
		||||
local e1 = world:entity()
 | 
			
		||||
world:delete(e)
 | 
			
		||||
 | 
			
		||||
world:print_snapshot()
 | 
			
		||||
local e2 = world:entity()
 | 
			
		||||
local e3 = world:entity()
 | 
			
		||||
world:print_snapshot()
 | 
			
		||||
world:delete(e1)
 | 
			
		||||
world:delete(e2)
 | 
			
		||||
world:delete(e3)
 | 
			
		||||
world:print_snapshot()
 | 
			
		||||
world:print_entities()
 | 
			
		||||
world:entity()
 | 
			
		||||
world:entity()
 | 
			
		||||
world:print_snapshot()
 | 
			
		||||
							
								
								
									
										193
									
								
								tools/lifetime_tracker.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								tools/lifetime_tracker.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,193 @@
 | 
			
		|||
local jecs = require("@jecs")
 | 
			
		||||
local ECS_GENERATION = jecs.ECS_GENERATION
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
    -- 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
 | 
			
		||||
 | 
			
		||||
local function lifetime_tracker_add(world: jecs.World)
 | 
			
		||||
	local entity_index = world.entity_index
 | 
			
		||||
	local dense_array = entity_index.dense_array
 | 
			
		||||
	local world_delete = world.delete
 | 
			
		||||
	local world_entity = world.entity
 | 
			
		||||
	local component_index = world.component_index
 | 
			
		||||
 | 
			
		||||
	local ENTITY_RANGE = (jecs.Rest :: any) + 1
 | 
			
		||||
 | 
			
		||||
	local w = setmetatable({}, { __index = world })
 | 
			
		||||
	w.delete = function(self, e)
 | 
			
		||||
		print("Entity deleted:", e)
 | 
			
		||||
 | 
			
		||||
		for child in world:each(pair(__, e)) do
 | 
			
		||||
 | 
			
		||||
		end
 | 
			
		||||
		return world_delete(world, e)
 | 
			
		||||
	end
 | 
			
		||||
	w.entity = function(self)
 | 
			
		||||
		local e = world_entity(world)
 | 
			
		||||
		print("Entity created:", pe(e))
 | 
			
		||||
		return e
 | 
			
		||||
	end
 | 
			
		||||
	w.print_entities = 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)
 | 
			
		||||
		end
 | 
			
		||||
		print("\n")
 | 
			
		||||
		print("|--dead--|")
 | 
			
		||||
		for i = 1, #dead do
 | 
			
		||||
			print_centered_entity(pe(dead[i]), 32)
 | 
			
		||||
			print(sep)
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	local timelines = {}
 | 
			
		||||
	w.print_snapshot = function(self)
 | 
			
		||||
		local timeline = #timelines + 1
 | 
			
		||||
		local entity_column_width = 10
 | 
			
		||||
		local status_column_width = 8
 | 
			
		||||
 | 
			
		||||
		local header = string.format("| %-" .. entity_column_width .. "s |", "Entity")
 | 
			
		||||
		for i = 1, timeline do
 | 
			
		||||
			header = header .. string.format(" %-" .. status_column_width .. "s |", string.format("T%d", i))
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		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 data = {}
 | 
			
		||||
		print("-------------------------------------------------------------------")
 | 
			
		||||
		print(header)
 | 
			
		||||
 | 
			
		||||
		-- Store the snapshot data for this timeline
 | 
			
		||||
		for i = ENTITY_RANGE, max_id do
 | 
			
		||||
			if dense_array[i] then
 | 
			
		||||
				local entity = dense_array[i]
 | 
			
		||||
				local id = ECS_ID(entity)
 | 
			
		||||
				local status = "alive"
 | 
			
		||||
				if id > alive_count then
 | 
			
		||||
					status = "dead"
 | 
			
		||||
				end
 | 
			
		||||
				data[id] = status
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		table.insert(timelines, data)
 | 
			
		||||
 | 
			
		||||
		-- Create a table to hold entity data for sorting
 | 
			
		||||
		local entities = {}
 | 
			
		||||
		for i = ENTITY_RANGE, max_id do
 | 
			
		||||
			if dense_array[i] then
 | 
			
		||||
				local entity = dense_array[i]
 | 
			
		||||
				local id = ECS_ID(entity)
 | 
			
		||||
				-- Push entity and id into the new `entities` table
 | 
			
		||||
				table.insert(entities, {entity = entity, id = id})
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- Sort the entities by ECS_ID
 | 
			
		||||
		table.sort(entities, function(a, b)
 | 
			
		||||
			return a.id < b.id
 | 
			
		||||
		end)
 | 
			
		||||
 | 
			
		||||
		-- Print the sorted rows
 | 
			
		||||
		for _, entity_data in ipairs(entities) do
 | 
			
		||||
			local entity = entity_data.entity
 | 
			
		||||
			local id = entity_data.id
 | 
			
		||||
			local status = "alive"
 | 
			
		||||
			if id > alive_count then
 | 
			
		||||
				status = "dead"
 | 
			
		||||
			end
 | 
			
		||||
			local row = string.format("| %-" .. entity_column_width .. "s     |", pe(entity))
 | 
			
		||||
			for j = 1, timeline do
 | 
			
		||||
				local timeline_data = timelines[j]
 | 
			
		||||
				local entity_data = timeline_data[id]
 | 
			
		||||
				if entity_data then
 | 
			
		||||
					row = row .. string.format(" %-" .. status_column_width .. "s |", entity_data)
 | 
			
		||||
				else
 | 
			
		||||
					row = row .. string.format(" %-" .. status_column_width .. "s |", "-")
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
			print(row)
 | 
			
		||||
		end
 | 
			
		||||
		print("-------------------------------------------------------------------")
 | 
			
		||||
	end
 | 
			
		||||
	return w
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
return lifetime_tracker_add
 | 
			
		||||
		Loading…
	
		Reference in a new issue