mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-03 18:39:19 +00:00 
			
		
		
		
	Fix line endings
This commit is contained in:
		
							parent
							
								
									bb03e88d3d
								
							
						
					
					
						commit
						b73bb0daee
					
				
					 21 changed files with 2102 additions and 2011 deletions
				
			
		| 
						 | 
					@ -1,49 +1,49 @@
 | 
				
			||||||
--!optimize 2
 | 
					--!optimize 2
 | 
				
			||||||
--!native
 | 
					--!native
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
 | 
					local Matter = require(ReplicatedStorage.DevPackages.Matter)
 | 
				
			||||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
 | 
					local ecr = require(ReplicatedStorage.DevPackages.ecr)
 | 
				
			||||||
local jecs = require(ReplicatedStorage.Lib)
 | 
					local jecs = require(ReplicatedStorage.Lib)
 | 
				
			||||||
local pair = jecs.pair
 | 
					local pair = jecs.pair
 | 
				
			||||||
local ecs = jecs.World.new()
 | 
					local ecs = jecs.World.new()
 | 
				
			||||||
local mirror = require(ReplicatedStorage.mirror)
 | 
					local mirror = require(ReplicatedStorage.mirror)
 | 
				
			||||||
local mcs = mirror.World.new()
 | 
					local mcs = mirror.World.new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local C1 = ecs:component()
 | 
					local C1 = ecs:component()
 | 
				
			||||||
local C2 = ecs:entity()
 | 
					local C2 = ecs:entity()
 | 
				
			||||||
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
					ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
local C3 = ecs:entity()
 | 
					local C3 = ecs:entity()
 | 
				
			||||||
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
					ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
local C4 = ecs:entity()
 | 
					local C4 = ecs:entity()
 | 
				
			||||||
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
					ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
local E1 = mcs:component()
 | 
					local E1 = mcs:component()
 | 
				
			||||||
local E2 = mcs:entity()
 | 
					local E2 = mcs:entity()
 | 
				
			||||||
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
					mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
local E3 = mcs:entity()
 | 
					local E3 = mcs:entity()
 | 
				
			||||||
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
					mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
local E4 = mcs:entity()
 | 
					local E4 = mcs:entity()
 | 
				
			||||||
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
					mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
return {
 | 
					return {
 | 
				
			||||||
	ParameterGenerator = function()
 | 
						ParameterGenerator = function()
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Functions = {
 | 
						Functions = {
 | 
				
			||||||
		Mirror = function()
 | 
							Mirror = function()
 | 
				
			||||||
			local m = mcs:entity()
 | 
								local m = mcs:entity()
 | 
				
			||||||
			for i = 1, 100 do
 | 
								for i = 1, 100 do
 | 
				
			||||||
				mcs:add(m, E3)
 | 
									mcs:add(m, E3)
 | 
				
			||||||
				mcs:remove(m, E3)
 | 
									mcs:remove(m, E3)
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		end,
 | 
							end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Jecs = function()
 | 
							Jecs = function()
 | 
				
			||||||
			local j = ecs:entity()
 | 
								local j = ecs:entity()
 | 
				
			||||||
			for i = 1, 100 do
 | 
								for i = 1, 100 do
 | 
				
			||||||
				ecs:add(j, C3)
 | 
									ecs:add(j, C3)
 | 
				
			||||||
				ecs:remove(j, C3)
 | 
									ecs:remove(j, C3)
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		end,
 | 
							end,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,28 +1,28 @@
 | 
				
			||||||
local function collect<T...>(
 | 
					local function collect<T...>(
 | 
				
			||||||
	signal: {
 | 
						signal: {
 | 
				
			||||||
		Connect: (RBXScriptSignal<T...>, fn: (T...) -> ()) -> RBXScriptConnection
 | 
							Connect: (RBXScriptSignal<T...>, fn: (T...) -> ()) -> RBXScriptConnection
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
): () -> (T...)
 | 
					): () -> (T...)
 | 
				
			||||||
	local enqueued = {}
 | 
						local enqueued = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local i = 0
 | 
						local i = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local connection = (signal :: any):Connect(function(...)
 | 
						local connection = (signal :: any):Connect(function(...)
 | 
				
			||||||
		table.insert(enqueued, { ... })
 | 
							table.insert(enqueued, { ... })
 | 
				
			||||||
		i += 1
 | 
							i += 1
 | 
				
			||||||
	end)
 | 
						end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return function(): any
 | 
						return function(): any
 | 
				
			||||||
		if i == 0 then
 | 
							if i == 0 then
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		i -= 1
 | 
							i -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local args: any = table.remove(enqueued, 1)
 | 
							local args: any = table.remove(enqueued, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return unpack(args)
 | 
							return unpack(args)
 | 
				
			||||||
	end, connection
 | 
						end, connection
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
return collect
 | 
					return collect
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,36 +1,36 @@
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local jecs = require(ReplicatedStorage.ecs)
 | 
					local jecs = require(ReplicatedStorage.ecs)
 | 
				
			||||||
local types = require("./types")
 | 
					local types = require("./types")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local Networked = jecs.tag()
 | 
					local Networked = jecs.tag()
 | 
				
			||||||
local NetworkedPair = jecs.tag()
 | 
					local NetworkedPair = jecs.tag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local Renderable = jecs.component() :: jecs.Id<Instance>
 | 
					local Renderable = jecs.component() :: jecs.Id<Instance>
 | 
				
			||||||
jecs.meta(Renderable, Networked)
 | 
					jecs.meta(Renderable, Networked)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local Poison = jecs.component() :: jecs.Id<number>
 | 
					local Poison = jecs.component() :: jecs.Id<number>
 | 
				
			||||||
jecs.meta(Poison, Networked)
 | 
					jecs.meta(Poison, Networked)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local Health = jecs.component() :: jecs.Id<number>
 | 
					local Health = jecs.component() :: jecs.Id<number>
 | 
				
			||||||
jecs.meta(Health, Networked)
 | 
					jecs.meta(Health, Networked)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local Player = jecs.component() :: jecs.Id<Player>
 | 
					local Player = jecs.component() :: jecs.Id<Player>
 | 
				
			||||||
jecs.meta(Player, Networked)
 | 
					jecs.meta(Player, Networked)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local components = {
 | 
					local components = {
 | 
				
			||||||
	Renderable = Renderable,
 | 
						Renderable = Renderable,
 | 
				
			||||||
	Player = Player,
 | 
						Player = Player,
 | 
				
			||||||
	Poison = Poison,
 | 
						Poison = Poison,
 | 
				
			||||||
	Health = Health,
 | 
						Health = Health,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Networked = Networked,
 | 
						Networked = Networked,
 | 
				
			||||||
	NetworkedPair = NetworkedPair,
 | 
						NetworkedPair = NetworkedPair,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for name, component in components do
 | 
					for name, component in components do
 | 
				
			||||||
	jecs.meta(component, jecs.Name, name)
 | 
						jecs.meta(component, jecs.Name, name)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
return components
 | 
					return components
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,190 +1,190 @@
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local jecs = require(ReplicatedStorage.ecs)
 | 
					local jecs = require(ReplicatedStorage.ecs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Observer<T...> = {
 | 
					type Observer<T...> = {
 | 
				
			||||||
	callback: (jecs.Entity) -> (),
 | 
						callback: (jecs.Entity) -> (),
 | 
				
			||||||
	query: jecs.Query<T...>,
 | 
						query: jecs.Query<T...>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type PatchedWorld = jecs.World & {
 | 
					export type PatchedWorld = jecs.World & {
 | 
				
			||||||
	added: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id<T>, value: T) -> ()) -> (),
 | 
						added: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id<T>, value: T) -> ()) -> (),
 | 
				
			||||||
	removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (),
 | 
						removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (),
 | 
				
			||||||
	changed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id<T>, value: T) -> ()) -> (),
 | 
						changed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id<T>, value: T) -> ()) -> (),
 | 
				
			||||||
	-- deleted: (PatchedWorld, () -> ()) -> () -> (),
 | 
						-- deleted: (PatchedWorld, () -> ()) -> () -> (),
 | 
				
			||||||
	observer: (PatchedWorld, Observer<any>) -> (),
 | 
						observer: (PatchedWorld, Observer<any>) -> (),
 | 
				
			||||||
	monitor: (PatchedWorld, Observer<any>) -> (),
 | 
						monitor: (PatchedWorld, Observer<any>) -> (),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function observers_new(world, description)
 | 
					local function observers_new(world, description)
 | 
				
			||||||
	local query = description.query
 | 
						local query = description.query
 | 
				
			||||||
	local callback = description.callback
 | 
						local callback = description.callback
 | 
				
			||||||
	local terms = query.filter_with :: { jecs.Id }
 | 
						local terms = query.filter_with :: { jecs.Id }
 | 
				
			||||||
	if not terms then
 | 
						if not terms then
 | 
				
			||||||
		local ids = query.ids
 | 
							local ids = query.ids
 | 
				
			||||||
		query.filter_with = ids
 | 
							query.filter_with = ids
 | 
				
			||||||
		terms = ids
 | 
							terms = ids
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local entity_index = world.entity_index :: any
 | 
						local entity_index = world.entity_index :: any
 | 
				
			||||||
	local function emplaced(entity: jecs.Entity)
 | 
						local function emplaced(entity: jecs.Entity)
 | 
				
			||||||
		local r = jecs.entity_index_try_get_fast(
 | 
							local r = jecs.entity_index_try_get_fast(
 | 
				
			||||||
			entity_index, entity :: any)
 | 
								entity_index, entity :: any)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if not r then
 | 
							if not r then
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local archetype = r.archetype
 | 
							local archetype = r.archetype
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if jecs.query_match(query, archetype) then
 | 
							if jecs.query_match(query, archetype) then
 | 
				
			||||||
			callback(entity)
 | 
								callback(entity)
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, term in terms do
 | 
						for _, term in terms do
 | 
				
			||||||
		world:added(term, emplaced)
 | 
							world:added(term, emplaced)
 | 
				
			||||||
		world:changed(term, emplaced)
 | 
							world:changed(term, emplaced)
 | 
				
			||||||
 	end
 | 
					 	end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function monitors_new(world, description)
 | 
					local function monitors_new(world, description)
 | 
				
			||||||
	local query = description.query
 | 
						local query = description.query
 | 
				
			||||||
	local callback = description.callback
 | 
						local callback = description.callback
 | 
				
			||||||
	local terms = query.filter_with :: { jecs.Id }
 | 
						local terms = query.filter_with :: { jecs.Id }
 | 
				
			||||||
	if not terms then
 | 
						if not terms then
 | 
				
			||||||
		local ids = query.ids
 | 
							local ids = query.ids
 | 
				
			||||||
		query.filter_with = ids
 | 
							query.filter_with = ids
 | 
				
			||||||
		terms = ids
 | 
							terms = ids
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local entity_index = world.entity_index :: any
 | 
						local entity_index = world.entity_index :: any
 | 
				
			||||||
	local function emplaced(entity: jecs.Entity)
 | 
						local function emplaced(entity: jecs.Entity)
 | 
				
			||||||
		local r = jecs.entity_index_try_get_fast(
 | 
							local r = jecs.entity_index_try_get_fast(
 | 
				
			||||||
			entity_index, entity :: any)
 | 
								entity_index, entity :: any)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if not r then
 | 
							if not r then
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local archetype = r.archetype
 | 
							local archetype = r.archetype
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if jecs.query_match(query, archetype) then
 | 
							if jecs.query_match(query, archetype) then
 | 
				
			||||||
			callback(entity, jecs.OnAdd)
 | 
								callback(entity, jecs.OnAdd)
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local function removed(entity: jecs.Entity, component: jecs.Id)
 | 
						local function removed(entity: jecs.Entity, component: jecs.Id)
 | 
				
			||||||
		local r = jecs.entity_index_try_get_fast(
 | 
							local r = jecs.entity_index_try_get_fast(
 | 
				
			||||||
			entity_index, entity :: any)
 | 
								entity_index, entity :: any)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if not r then
 | 
							if not r then
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local archetype = r.archetype
 | 
							local archetype = r.archetype
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if jecs.query_match(query, archetype) then
 | 
							if jecs.query_match(query, archetype) then
 | 
				
			||||||
			callback(entity, jecs.OnRemove)
 | 
								callback(entity, jecs.OnRemove)
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, term in terms do
 | 
						for _, term in terms do
 | 
				
			||||||
		world:added(term, emplaced)
 | 
							world:added(term, emplaced)
 | 
				
			||||||
		world:removed(term, removed)
 | 
							world:removed(term, removed)
 | 
				
			||||||
 	end
 | 
					 	end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function observers_add(world: jecs.World): PatchedWorld
 | 
					local function observers_add(world: jecs.World): PatchedWorld
 | 
				
			||||||
	local signals = {
 | 
						local signals = {
 | 
				
			||||||
		added = {},
 | 
							added = {},
 | 
				
			||||||
		emplaced = {},
 | 
							emplaced = {},
 | 
				
			||||||
		removed = {},
 | 
							removed = {},
 | 
				
			||||||
		deleted = {}
 | 
							deleted = {}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	world = world :: jecs.World & {[string]: any}
 | 
						world = world :: jecs.World & {[string]: any}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	world.added = function(_, component, fn)
 | 
						world.added = function(_, component, fn)
 | 
				
			||||||
		local listeners = signals.added[component]
 | 
							local listeners = signals.added[component]
 | 
				
			||||||
		if not listeners then
 | 
							if not listeners then
 | 
				
			||||||
			listeners = {}
 | 
								listeners = {}
 | 
				
			||||||
			signals.added[component] = listeners
 | 
								signals.added[component] = listeners
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			local idr = jecs.id_record_ensure(world :: any, component :: any)
 | 
								local idr = jecs.id_record_ensure(world :: any, component :: any)
 | 
				
			||||||
			local rw = jecs.pair(component, jecs.Wildcard)
 | 
								local rw = jecs.pair(component, jecs.Wildcard)
 | 
				
			||||||
			local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
 | 
								local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
 | 
				
			||||||
			local function on_add(entity: number, id: number, value: any)
 | 
								local function on_add(entity: number, id: number, value: any)
 | 
				
			||||||
				for _, listener in listeners do
 | 
									for _, listener in listeners do
 | 
				
			||||||
					listener(entity, id, value)
 | 
										listener(entity, id, value)
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
			world:set(component, jecs.OnAdd, on_add)
 | 
								world:set(component, jecs.OnAdd, on_add)
 | 
				
			||||||
			idr.hooks.on_add = on_add :: any
 | 
								idr.hooks.on_add = on_add :: any
 | 
				
			||||||
			idr_r.hooks.on_add = on_add :: any
 | 
								idr_r.hooks.on_add = on_add :: any
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		table.insert(listeners, fn)
 | 
							table.insert(listeners, fn)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	world.changed = function(_, component, fn)
 | 
						world.changed = function(_, component, fn)
 | 
				
			||||||
		local listeners = signals.emplaced[component]
 | 
							local listeners = signals.emplaced[component]
 | 
				
			||||||
		if not listeners then
 | 
							if not listeners then
 | 
				
			||||||
			listeners = {}
 | 
								listeners = {}
 | 
				
			||||||
			signals.emplaced[component] = listeners
 | 
								signals.emplaced[component] = listeners
 | 
				
			||||||
			local idr = jecs.id_record_ensure(world :: any, component :: any)
 | 
								local idr = jecs.id_record_ensure(world :: any, component :: any)
 | 
				
			||||||
			local rw = jecs.pair(component, jecs.Wildcard)
 | 
								local rw = jecs.pair(component, jecs.Wildcard)
 | 
				
			||||||
			local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
 | 
								local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
 | 
				
			||||||
			local function on_change(entity: number, id: number, value: any)
 | 
								local function on_change(entity: number, id: number, value: any)
 | 
				
			||||||
				for _, listener in listeners do
 | 
									for _, listener in listeners do
 | 
				
			||||||
					listener(entity, id, value)
 | 
										listener(entity, id, value)
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
			world:set(component, jecs.OnChange, on_change)
 | 
								world:set(component, jecs.OnChange, on_change)
 | 
				
			||||||
			idr.hooks.on_change = on_change :: any
 | 
								idr.hooks.on_change = on_change :: any
 | 
				
			||||||
			idr_r.hooks.on_change = on_change :: any
 | 
								idr_r.hooks.on_change = on_change :: any
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		table.insert(listeners, fn)
 | 
							table.insert(listeners, fn)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	world.removed = function(_, component, fn)
 | 
						world.removed = function(_, component, fn)
 | 
				
			||||||
		local listeners = signals.removed[component]
 | 
							local listeners = signals.removed[component]
 | 
				
			||||||
		if not listeners then
 | 
							if not listeners then
 | 
				
			||||||
			listeners = {}
 | 
								listeners = {}
 | 
				
			||||||
			signals.removed[component] = listeners
 | 
								signals.removed[component] = listeners
 | 
				
			||||||
			local idr = jecs.id_record_ensure(world :: any, component :: any)
 | 
								local idr = jecs.id_record_ensure(world :: any, component :: any)
 | 
				
			||||||
			local rw = jecs.pair(component, jecs.Wildcard)
 | 
								local rw = jecs.pair(component, jecs.Wildcard)
 | 
				
			||||||
			local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
 | 
								local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
 | 
				
			||||||
			local function on_remove(entity: number, id: number, value: any)
 | 
								local function on_remove(entity: number, id: number, value: any)
 | 
				
			||||||
				for _, listener in listeners do
 | 
									for _, listener in listeners do
 | 
				
			||||||
					listener(entity, id, value)
 | 
										listener(entity, id, value)
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
			world:set(component, jecs.OnRemove, on_remove)
 | 
								world:set(component, jecs.OnRemove, on_remove)
 | 
				
			||||||
			idr.hooks.on_remove = on_remove :: any
 | 
								idr.hooks.on_remove = on_remove :: any
 | 
				
			||||||
			idr_r.hooks.on_remove = on_remove :: any
 | 
								idr_r.hooks.on_remove = on_remove :: any
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		table.insert(listeners, fn)
 | 
							table.insert(listeners, fn)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	world.signals = signals
 | 
						world.signals = signals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	world.observer = observers_new
 | 
						world.observer = observers_new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	world.monitor = monitors_new
 | 
						world.monitor = monitors_new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	-- local world_delete = world.delete
 | 
						-- local world_delete = world.delete
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	-- world.deleted = function(_, fn)
 | 
						-- world.deleted = function(_, fn)
 | 
				
			||||||
	-- 	local listeners = signals.deleted
 | 
						-- 	local listeners = signals.deleted
 | 
				
			||||||
	-- 	table.insert(listeners, fn)
 | 
						-- 	table.insert(listeners, fn)
 | 
				
			||||||
	-- end
 | 
						-- end
 | 
				
			||||||
	-- world.delete = function(world, entity)
 | 
						-- world.delete = function(world, entity)
 | 
				
			||||||
	-- 	world_delete(world, entity)
 | 
						-- 	world_delete(world, entity)
 | 
				
			||||||
	-- 	for _, fn in signals.deleted do
 | 
						-- 	for _, fn in signals.deleted do
 | 
				
			||||||
	-- 		fn(entity)
 | 
						-- 		fn(entity)
 | 
				
			||||||
	-- 	end
 | 
						-- 	end
 | 
				
			||||||
	-- end
 | 
						-- end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return world :: PatchedWorld
 | 
						return world :: PatchedWorld
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
return observers_add
 | 
					return observers_add
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,50 +1,50 @@
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local types = require("../ReplicatedStorage/types")
 | 
					local types = require("../ReplicatedStorage/types")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Signal<T...> = {
 | 
					type Signal<T...> = {
 | 
				
			||||||
	Connect: (Signal<T...>, fn: (T...) -> ()) -> RBXScriptConnection
 | 
						Connect: (Signal<T...>, fn: (T...) -> ()) -> RBXScriptConnection
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
type Remote<T...> = {
 | 
					type Remote<T...> = {
 | 
				
			||||||
	FireClient: (Remote<T...>, T...) -> (),
 | 
						FireClient: (Remote<T...>, T...) -> (),
 | 
				
			||||||
	FireAllClients: (Remote<T...>, T...) -> (),
 | 
						FireAllClients: (Remote<T...>, T...) -> (),
 | 
				
			||||||
	FireServer: (Remote<T...>) -> (),
 | 
						FireServer: (Remote<T...>) -> (),
 | 
				
			||||||
	OnServerEvent: {
 | 
						OnServerEvent: {
 | 
				
			||||||
		Connect: (any, fn: (Player, T...) -> () ) -> ()
 | 
							Connect: (any, fn: (Player, T...) -> () ) -> ()
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	OnClientEvent: {
 | 
						OnClientEvent: {
 | 
				
			||||||
		Connect: (any, fn: (T...) -> () ) -> ()
 | 
							Connect: (any, fn: (T...) -> () ) -> ()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function stream_ensure(name): Remote<any>
 | 
					local function stream_ensure(name): Remote<any>
 | 
				
			||||||
	local remote = ReplicatedStorage:FindFirstChild(name)
 | 
						local remote = ReplicatedStorage:FindFirstChild(name)
 | 
				
			||||||
	if not remote then
 | 
						if not remote then
 | 
				
			||||||
		remote = Instance.new("RemoteEvent")
 | 
							remote = Instance.new("RemoteEvent")
 | 
				
			||||||
		remote.Name = name
 | 
							remote.Name = name
 | 
				
			||||||
		remote.Parent = ReplicatedStorage
 | 
							remote.Parent = ReplicatedStorage
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	return remote :: any
 | 
						return remote :: any
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function datagram_ensure(name): Remote<any>
 | 
					local function datagram_ensure(name): Remote<any>
 | 
				
			||||||
	local remote = ReplicatedStorage:FindFirstChild(name)
 | 
						local remote = ReplicatedStorage:FindFirstChild(name)
 | 
				
			||||||
	if not remote then
 | 
						if not remote then
 | 
				
			||||||
		remote = Instance.new("UnreliableRemoteEvent")
 | 
							remote = Instance.new("UnreliableRemoteEvent")
 | 
				
			||||||
		remote.Name = name
 | 
							remote.Name = name
 | 
				
			||||||
		remote.Parent = ReplicatedStorage
 | 
							remote.Parent = ReplicatedStorage
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	return remote :: any
 | 
						return remote :: any
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
return {
 | 
					return {
 | 
				
			||||||
	input = datagram_ensure("input") :: Remote<string>,
 | 
						input = datagram_ensure("input") :: Remote<string>,
 | 
				
			||||||
	replication = stream_ensure("replication") :: Remote<{
 | 
						replication = stream_ensure("replication") :: Remote<{
 | 
				
			||||||
		[string]: {
 | 
							[string]: {
 | 
				
			||||||
			set: { types.Entity }?,
 | 
								set: { types.Entity }?,
 | 
				
			||||||
			values: { any }?,
 | 
								values: { any }?,
 | 
				
			||||||
			removed: { types.Entity }?
 | 
								removed: { types.Entity }?
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}>,
 | 
						}>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,136 +1,136 @@
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local jabby = require(ReplicatedStorage.Packages.jabby)
 | 
					local jabby = require(ReplicatedStorage.Packages.jabby)
 | 
				
			||||||
local jecs = require(ReplicatedStorage.ecs)
 | 
					local jecs = require(ReplicatedStorage.ecs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jabby.set_check_function(function() return true end)
 | 
					jabby.set_check_function(function() return true end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local scheduler = jabby.scheduler.create("jabby scheduler")
 | 
					local scheduler = jabby.scheduler.create("jabby scheduler")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jabby.register({
 | 
					jabby.register({
 | 
				
			||||||
	applet = jabby.applets.scheduler,
 | 
						applet = jabby.applets.scheduler,
 | 
				
			||||||
	name = "Scheduler",
 | 
						name = "Scheduler",
 | 
				
			||||||
	configuration = {
 | 
						configuration = {
 | 
				
			||||||
		scheduler = scheduler,
 | 
							scheduler = scheduler,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local ContextActionService = game:GetService("ContextActionService")
 | 
					local ContextActionService = game:GetService("ContextActionService")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function create_widget(_, state: Enum.UserInputState)
 | 
					local function create_widget(_, state: Enum.UserInputState)
 | 
				
			||||||
	local client = jabby.obtain_client()
 | 
						local client = jabby.obtain_client()
 | 
				
			||||||
    if state ~= Enum.UserInputState.Begin then return end
 | 
					    if state ~= Enum.UserInputState.Begin then return end
 | 
				
			||||||
    client.spawn_app(client.apps.home, nil)
 | 
					    client.spawn_app(client.apps.home, nil)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local RunService = game:GetService("RunService")
 | 
					local RunService = game:GetService("RunService")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local System = jecs.component() :: jecs.Id<{
 | 
					local System = jecs.component() :: jecs.Id<{
 | 
				
			||||||
	fn: () -> (),
 | 
						fn: () -> (),
 | 
				
			||||||
	name: string,
 | 
						name: string,
 | 
				
			||||||
}>
 | 
					}>
 | 
				
			||||||
local DependsOn = jecs.component()
 | 
					local DependsOn = jecs.component()
 | 
				
			||||||
local Phase = jecs.tag()
 | 
					local Phase = jecs.tag()
 | 
				
			||||||
local Event = jecs.component() :: jecs.Id<RBXScriptSignal>
 | 
					local Event = jecs.component() :: jecs.Id<RBXScriptSignal>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local pair = jecs.pair
 | 
					local pair = jecs.pair
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local types = require(ReplicatedStorage.types)
 | 
					local types = require(ReplicatedStorage.types)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ECS_PHASE(world, after: types.Entity)
 | 
					local function ECS_PHASE(world, after: types.Entity)
 | 
				
			||||||
	local phase = world:entity()
 | 
						local phase = world:entity()
 | 
				
			||||||
	world:add(phase, Phase)
 | 
						world:add(phase, Phase)
 | 
				
			||||||
	if after then
 | 
						if after then
 | 
				
			||||||
		local dependency = pair(DependsOn, after)
 | 
							local dependency = pair(DependsOn, after)
 | 
				
			||||||
		world:add(phase, dependency)
 | 
							world:add(phase, dependency)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return phase
 | 
						return phase
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local Heartbeat = jecs.tag()
 | 
					local Heartbeat = jecs.tag()
 | 
				
			||||||
jecs.meta(Heartbeat, Phase)
 | 
					jecs.meta(Heartbeat, Phase)
 | 
				
			||||||
jecs.meta(Heartbeat, Event, RunService.Heartbeat)
 | 
					jecs.meta(Heartbeat, Event, RunService.Heartbeat)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local PreSimulation = jecs.tag()
 | 
					local PreSimulation = jecs.tag()
 | 
				
			||||||
jecs.meta(PreSimulation, Phase)
 | 
					jecs.meta(PreSimulation, Phase)
 | 
				
			||||||
jecs.meta(PreSimulation, Event, RunService.PreSimulation)
 | 
					jecs.meta(PreSimulation, Event, RunService.PreSimulation)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local PreAnimation = jecs.tag()
 | 
					local PreAnimation = jecs.tag()
 | 
				
			||||||
jecs.meta(PreAnimation, Phase)
 | 
					jecs.meta(PreAnimation, Phase)
 | 
				
			||||||
jecs.meta(PreAnimation, Event, RunService.PreAnimation)
 | 
					jecs.meta(PreAnimation, Event, RunService.PreAnimation)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local PreRender = jecs.tag()
 | 
					local PreRender = jecs.tag()
 | 
				
			||||||
jecs.meta(PreRender, Phase)
 | 
					jecs.meta(PreRender, Phase)
 | 
				
			||||||
jecs.meta(PreRender, Event, RunService.PreRender)
 | 
					jecs.meta(PreRender, Event, RunService.PreRender)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ECS_SYSTEM(world: types.World, mod: ModuleScript, phase: types.Entity?)
 | 
					local function ECS_SYSTEM(world: types.World, mod: ModuleScript, phase: types.Entity?)
 | 
				
			||||||
	local system = world:entity()
 | 
						local system = world:entity()
 | 
				
			||||||
	local p = phase or Heartbeat
 | 
						local p = phase or Heartbeat
 | 
				
			||||||
	local fn = require(mod) :: (...any) -> ()
 | 
						local fn = require(mod) :: (...any) -> ()
 | 
				
			||||||
	world:set(system, System, {
 | 
						world:set(system, System, {
 | 
				
			||||||
		fn = fn(world, 0) or fn,
 | 
							fn = fn(world, 0) or fn,
 | 
				
			||||||
		name = mod.Name,
 | 
							name = mod.Name,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local depends_on = DependsOn :: jecs.Entity
 | 
						local depends_on = DependsOn :: jecs.Entity
 | 
				
			||||||
	world:add(system, pair(depends_on, p))
 | 
						world:add(system, pair(depends_on, p))
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
local function find_systems_w_phase(world: types.World, systems, phase: types.Entity)
 | 
					local function find_systems_w_phase(world: types.World, systems, phase: types.Entity)
 | 
				
			||||||
	local phase_name = world:get(phase, jecs.Name) :: string
 | 
						local phase_name = world:get(phase, jecs.Name) :: string
 | 
				
			||||||
	for _, s in world:query(System):with(pair(DependsOn, phase)) do
 | 
						for _, s in world:query(System):with(pair(DependsOn, phase)) do
 | 
				
			||||||
		table.insert(systems, {
 | 
							table.insert(systems, {
 | 
				
			||||||
			id = scheduler:register_system({
 | 
								id = scheduler:register_system({
 | 
				
			||||||
				phase = phase_name,
 | 
									phase = phase_name,
 | 
				
			||||||
				name = s.name,
 | 
									name = s.name,
 | 
				
			||||||
			}),
 | 
								}),
 | 
				
			||||||
			fn = s.fn
 | 
								fn = s.fn
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	for after in world:query(Phase, pair(DependsOn, phase)) do
 | 
						for after in world:query(Phase, pair(DependsOn, phase)) do
 | 
				
			||||||
		find_systems_w_phase(world, systems, after)
 | 
							find_systems_w_phase(world, systems, after)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	return systems
 | 
						return systems
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ECS_RUN(world: types.World)
 | 
					local function ECS_RUN(world: types.World)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	jabby.register({
 | 
						jabby.register({
 | 
				
			||||||
		applet = jabby.applets.world,
 | 
							applet = jabby.applets.world,
 | 
				
			||||||
		name = "MyWorld",
 | 
							name = "MyWorld",
 | 
				
			||||||
		configuration = {
 | 
							configuration = {
 | 
				
			||||||
			world = world,
 | 
								world = world,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if RunService:IsClient() then
 | 
						if RunService:IsClient() then
 | 
				
			||||||
		ContextActionService:BindAction("Open Jabby Home", create_widget, false, Enum.KeyCode.F4)
 | 
							ContextActionService:BindAction("Open Jabby Home", create_widget, false, Enum.KeyCode.F4)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for phase, event in world:query(Event, Phase) do
 | 
						for phase, event in world:query(Event, Phase) do
 | 
				
			||||||
		local systems = find_systems_w_phase(world, {}, phase)
 | 
							local systems = find_systems_w_phase(world, {}, phase)
 | 
				
			||||||
		event:Connect(function(...)
 | 
							event:Connect(function(...)
 | 
				
			||||||
			for _, system in systems do
 | 
								for _, system in systems do
 | 
				
			||||||
				scheduler:run(system.id, system.fn, world, ...)
 | 
									scheduler:run(system.id, system.fn, world, ...)
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		end)
 | 
							end)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
return {
 | 
					return {
 | 
				
			||||||
	PHASE = ECS_PHASE,
 | 
						PHASE = ECS_PHASE,
 | 
				
			||||||
	SYSTEM = ECS_SYSTEM,
 | 
						SYSTEM = ECS_SYSTEM,
 | 
				
			||||||
	RUN = ECS_RUN,
 | 
						RUN = ECS_RUN,
 | 
				
			||||||
	phases = {
 | 
						phases = {
 | 
				
			||||||
		Heartbeat = Heartbeat,
 | 
							Heartbeat = Heartbeat,
 | 
				
			||||||
		PreSimulation = PreSimulation,
 | 
							PreSimulation = PreSimulation,
 | 
				
			||||||
		PreAnimation = PreAnimation,
 | 
							PreAnimation = PreAnimation,
 | 
				
			||||||
		PreRender = PreRender
 | 
							PreRender = PreRender
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	components = {
 | 
						components = {
 | 
				
			||||||
		System = System,
 | 
							System = System,
 | 
				
			||||||
		DependsOn = DependsOn,
 | 
							DependsOn = DependsOn,
 | 
				
			||||||
		Phase = Phase,
 | 
							Phase = Phase,
 | 
				
			||||||
		Event = Event,
 | 
							Event = Event,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,71 +1,86 @@
 | 
				
			||||||
local types = require("../types")
 | 
					local types = require("../types")
 | 
				
			||||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
 | 
					local jecs = require(game:GetService("ReplicatedStorage").ecs)
 | 
				
			||||||
local remotes = require("../remotes")
 | 
					local remotes = require("../remotes")
 | 
				
			||||||
local collect = require("../collect")
 | 
					local collect = require("../collect")
 | 
				
			||||||
local client_ids = {}
 | 
					local client_ids = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ecs_map_get(world: types.World, id: types.Entity)
 | 
					
 | 
				
			||||||
	local deserialised_id = client_ids[id]
 | 
					local function ecs_map_get(world, id)
 | 
				
			||||||
	if not deserialised_id then
 | 
						local deserialised_id = client_ids[id]
 | 
				
			||||||
		if world:has(id, jecs.Name) then
 | 
					
 | 
				
			||||||
			deserialised_id = world:entity(id)
 | 
						if not deserialised_id then
 | 
				
			||||||
		else
 | 
							if world:has(id, jecs.Name) then
 | 
				
			||||||
			if world:exists(id) then
 | 
								deserialised_id = world:entity(id)
 | 
				
			||||||
				deserialised_id = world:entity()
 | 
							else
 | 
				
			||||||
			else
 | 
								deserialised_id = world:entity()
 | 
				
			||||||
				deserialised_id = world:entity(id)
 | 
							end
 | 
				
			||||||
			end
 | 
					
 | 
				
			||||||
		end
 | 
							client_ids[id] = deserialised_id
 | 
				
			||||||
		client_ids[id] = deserialised_id
 | 
						end
 | 
				
			||||||
	end
 | 
					
 | 
				
			||||||
	return deserialised_id
 | 
						-- local deserialised_id = client_ids[id]
 | 
				
			||||||
end
 | 
						-- if not deserialised_id then
 | 
				
			||||||
 | 
						-- 	if world:has(id, jecs.Name) then
 | 
				
			||||||
local function ecs_make_alive_id(world: types.World, id: jecs.Id)
 | 
						-- 		deserialised_id = world:entity(id)
 | 
				
			||||||
	local rel = jecs.ECS_PAIR_FIRST(id)
 | 
						-- 	else
 | 
				
			||||||
	local tgt = jecs.ECS_PAIR_SECOND(id)
 | 
						-- 		if world:exists(id) then
 | 
				
			||||||
 | 
						-- 			deserialised_id = world:entity()
 | 
				
			||||||
	ecs_map_get(world, rel)
 | 
						-- 		else
 | 
				
			||||||
	ecs_map_get(world, tgt)
 | 
						-- 			deserialised_id = world:entity(id)
 | 
				
			||||||
end
 | 
						-- 		end
 | 
				
			||||||
 | 
						-- 	end
 | 
				
			||||||
local snapshots = collect(remotes.replication.OnClientEvent)
 | 
						-- 	client_ids[id] = deserialised_id
 | 
				
			||||||
 | 
						-- end
 | 
				
			||||||
return function(world: types.World)
 | 
					
 | 
				
			||||||
    return function()
 | 
						return deserialised_id
 | 
				
			||||||
        for snapshot in snapshots do
 | 
					end
 | 
				
			||||||
            for key, map in snapshot do
 | 
					
 | 
				
			||||||
            	local id = (tonumber(key) :: any) :: jecs.Id
 | 
					local function ecs_make_alive_id(world, id)
 | 
				
			||||||
                if jecs.IS_PAIR(id) then
 | 
						local rel = jecs.ECS_PAIR_FIRST(id)
 | 
				
			||||||
                	ecs_make_alive_id(world, id)
 | 
						local tgt = jecs.ECS_PAIR_SECOND(id)
 | 
				
			||||||
                end
 | 
					
 | 
				
			||||||
 | 
						rel = ecs_map_get(world, rel)
 | 
				
			||||||
                local set = map.set
 | 
						tgt = ecs_map_get(world, tgt)
 | 
				
			||||||
                if set then
 | 
					
 | 
				
			||||||
	                if jecs.is_tag(world, id) then
 | 
						return jecs.pair(rel, tgt)
 | 
				
			||||||
                       	for _, entity in set do
 | 
					end
 | 
				
			||||||
                        	entity = ecs_map_get(world, entity)
 | 
					
 | 
				
			||||||
                      		world:add(entity, id)
 | 
					local snapshots = collect(remotes.replication.OnClientEvent)
 | 
				
			||||||
                       	end
 | 
					
 | 
				
			||||||
                    else
 | 
					return function(world: types.World)
 | 
				
			||||||
		                local values = map.values :: { any }
 | 
					    for snapshot in snapshots do
 | 
				
			||||||
		                for i, entity in set do
 | 
					        for id, map in snapshot do
 | 
				
			||||||
							entity = ecs_map_get(world, entity)
 | 
					        	id = tonumber(id)
 | 
				
			||||||
                      		world:set(entity, id, values[i])
 | 
					            if jecs.IS_PAIR(id) then
 | 
				
			||||||
                       	end
 | 
					            	id = ecs_make_alive_id(world, id)
 | 
				
			||||||
				    end
 | 
					            end
 | 
				
			||||||
                end
 | 
					
 | 
				
			||||||
 | 
					            local set = map.set
 | 
				
			||||||
                local removed = map.removed
 | 
					            if set then
 | 
				
			||||||
                if removed then
 | 
					                if jecs.is_tag(world, id) then
 | 
				
			||||||
                    for i, e in removed do
 | 
					                   	for _, entity in set do
 | 
				
			||||||
                        if not world:contains(e) then
 | 
					                       	entity = ecs_map_get(world, entity)
 | 
				
			||||||
                            continue
 | 
					                  		world:add(entity, id)
 | 
				
			||||||
                        end
 | 
					                   	end
 | 
				
			||||||
                        world:remove(e, id)
 | 
					                else
 | 
				
			||||||
                    end
 | 
						                local values = map.values
 | 
				
			||||||
                end
 | 
						                for i, entity in set do
 | 
				
			||||||
            end
 | 
											entity = ecs_map_get(world, entity)
 | 
				
			||||||
        end
 | 
					                  		world:set(entity, id, values[i])
 | 
				
			||||||
    end
 | 
					                   	end
 | 
				
			||||||
end
 | 
								    end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            local removed = map.removed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if removed then
 | 
				
			||||||
 | 
					                for i, e in removed do
 | 
				
			||||||
 | 
					                    if not world:contains(e) then
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    end
 | 
				
			||||||
 | 
					                    world:remove(e, id)
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,15 @@
 | 
				
			||||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
 | 
					local jecs = require(game:GetService("ReplicatedStorage").ecs)
 | 
				
			||||||
local observers_add = require("../ReplicatedStorage/observers_add")
 | 
					local observers_add = require("../ReplicatedStorage/observers_add")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type World = typeof(observers_add(jecs.world()))
 | 
					export type World = typeof(observers_add(jecs.world()))
 | 
				
			||||||
export type Entity = jecs.Entity
 | 
					export type Entity = jecs.Entity
 | 
				
			||||||
export type Id<T> = jecs.Id<T>
 | 
					export type Id<T> = jecs.Id<T>
 | 
				
			||||||
 | 
					export type Snapshot = {
 | 
				
			||||||
return {}
 | 
						[string]: {
 | 
				
			||||||
 | 
							set: { jecs.Entity }?,
 | 
				
			||||||
 | 
							values: { any }?,
 | 
				
			||||||
 | 
							removed: { jecs.Entity }?
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local ct = require(ReplicatedStorage.components)
 | 
					local ct = require(ReplicatedStorage.components)
 | 
				
			||||||
local types = require(ReplicatedStorage.types)
 | 
					local types = require(ReplicatedStorage.types)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
return function(world: types.World, dt: number)
 | 
					return function(world: types.World, dt: number)
 | 
				
			||||||
	for e in world:query(ct.Player):without(ct.Health) do
 | 
						for e in world:query(ct.Player):without(ct.Health) do
 | 
				
			||||||
		world:set(e, ct.Health, 100)
 | 
							world:set(e, ct.Health, 100)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	for e in world:query(ct.Player, ct.Health):without(ct.Poison) do
 | 
						for e in world:query(ct.Player, ct.Health):without(ct.Poison) do
 | 
				
			||||||
		world:set(e, ct.Poison, 10)
 | 
							world:set(e, ct.Poison, 10)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,20 @@
 | 
				
			||||||
local collect = require("../../ReplicatedStorage/collect")
 | 
					local collect = require("../../ReplicatedStorage/collect")
 | 
				
			||||||
local types = require("../../ReplicatedStorage/types")
 | 
					local types = require("../../ReplicatedStorage/types")
 | 
				
			||||||
local ct = require("../../ReplicatedStorage/components")
 | 
					local ct = require("../../ReplicatedStorage/components")
 | 
				
			||||||
local Players = game:GetService("Players")
 | 
					local Players = game:GetService("Players")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local player_added = collect(Players.PlayerAdded)
 | 
					local player_added = collect(Players.PlayerAdded)
 | 
				
			||||||
return function(world: types.World, dt: number)
 | 
					return function(world: types.World, dt: number)
 | 
				
			||||||
	for player in player_added do
 | 
						for player in player_added do
 | 
				
			||||||
		local entity = world:entity()
 | 
							local entity = world:entity()
 | 
				
			||||||
		world:set(entity, ct.Player, player)
 | 
							world:set(entity, ct.Player, player)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for entity, player in world:query(ct.Player):without(ct.Renderable) do
 | 
						for entity, player in world:query(ct.Player):without(ct.Renderable) do
 | 
				
			||||||
		local character = player.Character
 | 
							local character = player.Character
 | 
				
			||||||
		if character then
 | 
							if character then
 | 
				
			||||||
		if not character.Parent then
 | 
							if not character.Parent then
 | 
				
			||||||
			world:set(entity, ct.Renderable, character)
 | 
								world:set(entity, ct.Renderable, character)
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local ct = require(ReplicatedStorage.components)
 | 
					local ct = require(ReplicatedStorage.components)
 | 
				
			||||||
return function(world, dt)
 | 
					return function(world, dt)
 | 
				
			||||||
	for e, poison, health in world:query(ct.Poison, ct.Health) do
 | 
						for e, poison, health in world:query(ct.Poison, ct.Health) do
 | 
				
			||||||
		local health_after_tick = health - poison * dt * 0.05
 | 
							local health_after_tick = health - poison * dt * 0.05
 | 
				
			||||||
		if health_after_tick < 0 then
 | 
							if health_after_tick < 0 then
 | 
				
			||||||
			world:remove(e, ct.Health)
 | 
								world:remove(e, ct.Health)
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		world:set(e, ct.Health, health_after_tick)
 | 
							world:set(e, ct.Health, health_after_tick)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,122 +1,190 @@
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local types = require("../../ReplicatedStorage/types")
 | 
					local types = require("../../ReplicatedStorage/types")
 | 
				
			||||||
local ct = require("../../ReplicatedStorage/components")
 | 
					local ct = require("../../ReplicatedStorage/components")
 | 
				
			||||||
local jecs = require(ReplicatedStorage.ecs)
 | 
					local jecs = require(ReplicatedStorage.ecs)
 | 
				
			||||||
local remotes = require("../../ReplicatedStorage/remotes")
 | 
					local remotes = require("../../ReplicatedStorage/remotes")
 | 
				
			||||||
 | 
					local components = ct :: {[string]: jecs.Entity }
 | 
				
			||||||
return function(world: types.World)
 | 
					
 | 
				
			||||||
    local storages = {}
 | 
					return function(world: ty.World)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for component in world:query(ct.Networked) do
 | 
						--- integration test
 | 
				
			||||||
        local is_tag = jecs.is_tag(world, component)
 | 
					
 | 
				
			||||||
        local storage = {} :: { [types.Entity]: any }
 | 
					 --    for _ = 1, 10 do
 | 
				
			||||||
        storages[component] = storage
 | 
					 --    	local e = world:entity()
 | 
				
			||||||
 | 
					 --     	world:set(e, ct.TestA, true)
 | 
				
			||||||
        if is_tag then
 | 
					 --    end
 | 
				
			||||||
            world:added(component, function(entity)
 | 
					
 | 
				
			||||||
                storage[entity] = true
 | 
					    local storages = {} :: { [jecs.Entity]: {[jecs.Entity]: any }}
 | 
				
			||||||
            end)
 | 
					    local networked_components = {}
 | 
				
			||||||
        else
 | 
					    local networked_pairs = {}
 | 
				
			||||||
            world:added(component, function(entity, _, value)
 | 
					
 | 
				
			||||||
                storage[entity] = value
 | 
					    for component in world:each(ct.Networked) do
 | 
				
			||||||
            end)
 | 
					    	local name = world:get(component, jecs.Name) :: string
 | 
				
			||||||
            world:changed(component, function(entity, _, value)
 | 
							if components[name] == nil then
 | 
				
			||||||
            	storage[entity] = value
 | 
								continue
 | 
				
			||||||
            end)
 | 
							end
 | 
				
			||||||
        end
 | 
					
 | 
				
			||||||
 | 
					        storages[component] = {}
 | 
				
			||||||
        world:removed(component, function(entity)
 | 
					
 | 
				
			||||||
            storage[entity] = "jecs.Remove"
 | 
					        table.insert(networked_components, component)
 | 
				
			||||||
        end)
 | 
					    end
 | 
				
			||||||
    end
 | 
					
 | 
				
			||||||
 | 
						for relation in world:each(ct.NetworkedPair) do
 | 
				
			||||||
    for relation in world:query(ct.NetworkedPair) do
 | 
							local name = world:get(relation, jecs.Name) :: string
 | 
				
			||||||
        world:added(relation, function(entity, id, value)
 | 
							if not components[name] then
 | 
				
			||||||
            local is_tag = jecs.is_tag(world, id)
 | 
								continue
 | 
				
			||||||
            local storage = storages[id]
 | 
							end
 | 
				
			||||||
            if not storage then
 | 
							table.insert(networked_pairs, relation)
 | 
				
			||||||
                storage = {}
 | 
						end
 | 
				
			||||||
                storages[id] = storage
 | 
					
 | 
				
			||||||
            end
 | 
						for _, component in networked_components do
 | 
				
			||||||
            if is_tag then
 | 
							local name = world:get(component, jecs.Name) :: string
 | 
				
			||||||
                storage[entity] = true
 | 
							if not components[name] then
 | 
				
			||||||
            else
 | 
								error(`Networked Component (%id{component}%name{name})`)
 | 
				
			||||||
                storage[entity] = value
 | 
							end
 | 
				
			||||||
            end
 | 
							local is_tag = jecs.is_tag(world, component)
 | 
				
			||||||
        end)
 | 
							local storage = storages[component]
 | 
				
			||||||
 | 
							if is_tag then
 | 
				
			||||||
        world:changed(relation, function(entity, id, value)
 | 
							    world:added(component, function(entity)
 | 
				
			||||||
            local is_tag = jecs.is_tag(world, id)
 | 
							        storage[entity] = true
 | 
				
			||||||
            if is_tag then
 | 
							    end)
 | 
				
			||||||
                return
 | 
							else
 | 
				
			||||||
            end
 | 
							    world:added(component, function(entity, _, value)
 | 
				
			||||||
 | 
							        storage[entity] = value
 | 
				
			||||||
            local storage = storages[id]
 | 
							    end)
 | 
				
			||||||
            if not storage then
 | 
							    world:changed(component, function(entity, _, value)
 | 
				
			||||||
                storage = {}
 | 
							        storage[entity] = value
 | 
				
			||||||
                storages[id] = storage
 | 
							    end)
 | 
				
			||||||
            end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            storage[entity] = value
 | 
					        world:removed(component, function(entity)
 | 
				
			||||||
        end :: <T>(types.Entity, types.Id<T>, T) -> ())
 | 
					            storage[entity] = "jecs.Remove"
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
        world:removed(relation, function(entity, id)
 | 
						end
 | 
				
			||||||
            local storage = storages[id]
 | 
					
 | 
				
			||||||
            if not storage then
 | 
						for _, relation in networked_pairs do
 | 
				
			||||||
                storage = {}
 | 
							world:added(relation, function(entity, id, value)
 | 
				
			||||||
                storages[id] = storage
 | 
						        local is_tag = jecs.is_tag(world, id)
 | 
				
			||||||
            end
 | 
						        local storage = storages[id]
 | 
				
			||||||
 | 
						        if not storage then
 | 
				
			||||||
            storage[entity] = "jecs.Remove"
 | 
						            storage = {}
 | 
				
			||||||
        end)
 | 
						            storages[id] = storage
 | 
				
			||||||
    end
 | 
						        end
 | 
				
			||||||
 | 
						        if is_tag then
 | 
				
			||||||
    return function()
 | 
						            storage[entity] = true
 | 
				
			||||||
        local snapshot = {} :: {
 | 
						        else
 | 
				
			||||||
        	[string]: {
 | 
						            storage[entity] = value
 | 
				
			||||||
         		set: { types.Entity }?,
 | 
						        end
 | 
				
			||||||
           		values: { any }?,
 | 
						    end)
 | 
				
			||||||
             	removed: { types.Entity }?
 | 
					
 | 
				
			||||||
         	}
 | 
						    world:changed(relation, function(entity, id, value)
 | 
				
			||||||
        }
 | 
						        local is_tag = jecs.is_tag(world, id)
 | 
				
			||||||
 | 
						        if is_tag then
 | 
				
			||||||
        local set_ids = {} :: { types.Entity }
 | 
						            return
 | 
				
			||||||
        local removed_ids = {} :: { types.Entity }
 | 
						        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for component, storage in storages do
 | 
						        local storage = storages[id]
 | 
				
			||||||
            local set_values = {}
 | 
						        if not storage then
 | 
				
			||||||
            local set_n = 0
 | 
						            storage = {}
 | 
				
			||||||
            local removed_n = 0
 | 
						            storages[id] = storage
 | 
				
			||||||
            for e, v in storage do
 | 
						        end
 | 
				
			||||||
                if v ~= "jecs.Remove" then
 | 
					
 | 
				
			||||||
                    set_n += 1
 | 
						        storage[entity] = value
 | 
				
			||||||
                    set_ids[set_n] = e
 | 
						    end)
 | 
				
			||||||
                    set_values[set_n] = v or true
 | 
					
 | 
				
			||||||
                elseif world:contains(e) then
 | 
						    world:removed(relation, function(entity, id)
 | 
				
			||||||
                    removed_n += 1
 | 
						        local storage = storages[id]
 | 
				
			||||||
                    removed_ids[removed_n] = e
 | 
						        if not storage then
 | 
				
			||||||
                end
 | 
						            storage = {}
 | 
				
			||||||
            end
 | 
						            storages[id] = storage
 | 
				
			||||||
 | 
						        end
 | 
				
			||||||
            table.clear(storage)
 | 
					
 | 
				
			||||||
 | 
						        storage[entity] = "jecs.Remove"
 | 
				
			||||||
            local dirty = false
 | 
						    end)
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
            if set_n > 0 or removed_n > 0 then
 | 
					
 | 
				
			||||||
            	dirty = true
 | 
					    local players_added = collect(Players.PlayerAdded)
 | 
				
			||||||
            end
 | 
					
 | 
				
			||||||
 | 
					    return function()
 | 
				
			||||||
            if dirty then
 | 
					        local snapshot_lazy: ty.Snapshot
 | 
				
			||||||
	            snapshot[tostring(component)] = {
 | 
					        local set_ids_lazy: { jecs.Entity }
 | 
				
			||||||
                    set = if set_n > 0 then table.move(set_ids, 1, set_n, 1, {}) else nil,
 | 
					
 | 
				
			||||||
                    values = if set_n > 0 then set_values else nil,
 | 
							for player in players_added do
 | 
				
			||||||
                    removed = if removed_n > 0 then table.move(removed_ids, 1, removed_n, 1, {} :: { types.Entity }) else nil
 | 
								if not snapshot_lazy then
 | 
				
			||||||
                } :: any
 | 
									snapshot_lazy, set_ids_lazy =  {}, {}
 | 
				
			||||||
	        end
 | 
					
 | 
				
			||||||
        end
 | 
									for component, storage in storages do
 | 
				
			||||||
 | 
										local set_values = {}
 | 
				
			||||||
        if next(snapshot) ~= nil then
 | 
										local set_n = 0
 | 
				
			||||||
        	remotes.replication:FireAllClients(snapshot)
 | 
					
 | 
				
			||||||
        end
 | 
										local q = world:query(component)
 | 
				
			||||||
    end
 | 
										local is_tag = jecs.is_tag(world, component)
 | 
				
			||||||
end
 | 
										for _, archetype in q:archetypes() do
 | 
				
			||||||
 | 
											local entities = archetype.entities
 | 
				
			||||||
 | 
											local entities_len = #entities
 | 
				
			||||||
 | 
											table.move(entities, 1, entities_len, set_n + 1, set_ids_lazy)
 | 
				
			||||||
 | 
											if is_tag then
 | 
				
			||||||
 | 
												set_values = table.create(entities_len, true)
 | 
				
			||||||
 | 
											else
 | 
				
			||||||
 | 
												local column = archetype.columns[archetype.records[component]]
 | 
				
			||||||
 | 
												table.move(column, 1, entities_len, set_n + 1, set_values)
 | 
				
			||||||
 | 
											end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											set_n += entities_len
 | 
				
			||||||
 | 
										end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										local set = table.move(set_ids_lazy, 1, set_n, 1, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										snapshot_lazy[tostring(component)] = {
 | 
				
			||||||
 | 
										    set = if set_n > 0 then set else nil,
 | 
				
			||||||
 | 
										    values = if set_n > 0 then set_values else nil,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								remotes.replication:FireClient(player, snapshot_lazy)
 | 
				
			||||||
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local snapshot = {} :: ty.Snapshot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local set_ids = {}
 | 
				
			||||||
 | 
							local removed_ids = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for component, storage in storages do
 | 
				
			||||||
 | 
							    local set_values = {} :: { any }
 | 
				
			||||||
 | 
							    local set_n = 0
 | 
				
			||||||
 | 
							    local removed_n = 0
 | 
				
			||||||
 | 
							    for e, v in storage do
 | 
				
			||||||
 | 
							        if v ~= "jecs.Remove" then
 | 
				
			||||||
 | 
							            set_n += 1
 | 
				
			||||||
 | 
							            set_ids[set_n] = e
 | 
				
			||||||
 | 
							            set_values[set_n] = v or true
 | 
				
			||||||
 | 
							        elseif not world:contains(e) then
 | 
				
			||||||
 | 
							            removed_n += 1
 | 
				
			||||||
 | 
							            removed_ids[removed_n] = e
 | 
				
			||||||
 | 
							        end
 | 
				
			||||||
 | 
							    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    table.clear(storage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            local dirty = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if set_n > 0 or removed_n > 0 then
 | 
				
			||||||
 | 
					            	dirty = true
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if dirty then
 | 
				
			||||||
 | 
					            	local removed = table.move(removed_ids, 1, removed_n, 1, {}) :: { jecs.Entity }
 | 
				
			||||||
 | 
					             	local set = table.move(set_ids, 1, set_n, 1, {}) :: { jecs.Entity }
 | 
				
			||||||
 | 
						            snapshot[tostring(component)] = {
 | 
				
			||||||
 | 
					                    set = if set_n > 0 then set else nil,
 | 
				
			||||||
 | 
					                    values = if set_n > 0 then set_values else nil,
 | 
				
			||||||
 | 
					                    removed = if removed_n > 0 then removed else nil
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
						        end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        if next(snapshot) ~= nil then
 | 
				
			||||||
 | 
					        	remotes.replication:FireAllClients(snapshot)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,21 @@
 | 
				
			||||||
# Contribution Guidelines
 | 
					# Contribution Guidelines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Whether you found an issue, or want to make a change to jecs, we'd love to hear back from the community on what features you want or bugs you've run into.
 | 
					Whether you found an issue, or want to make a change to jecs, we'd love to hear back from the community on what features you want or bugs you've run into.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
There's a few different ways you can go about this.
 | 
					There's a few different ways you can go about this.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Creating an Issue
 | 
					## Creating an Issue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This is what you should be filing if you have a bug you want to report.
 | 
					This is what you should be filing if you have a bug you want to report.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Click here](https://github.com/Ukendio/jecs/issues/new/choose) to file a bug report. We have a few templates ready for the most common issue types.
 | 
					[Click here](https://github.com/Ukendio/jecs/issues/new/choose) to file a bug report. We have a few templates ready for the most common issue types.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Additionally, see the [Submitting Issues](../contributing/issues) page for more information.
 | 
					Additionally, see the [Submitting Issues](../contributing/issues) page for more information.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Creating a Pull Request
 | 
					## Creating a Pull Request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This is what you should be filing if you have a change you want to merge into the main project.
 | 
					This is what you should be filing if you have a change you want to merge into the main project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Click here](https://github.com/Ukendio/jecs/compare) to select the branch you want to merge from.
 | 
					[Click here](https://github.com/Ukendio/jecs/compare) to select the branch you want to merge from.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Additionally, see the [Submitting Pull Requests](../contributing/pull-requests) page for more information.
 | 
					Additionally, see the [Submitting Pull Requests](../contributing/pull-requests) page for more information.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,24 @@
 | 
				
			||||||
# Submitting Issues
 | 
					# Submitting Issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When you're submitting an issue, generally they fall into a few categories:
 | 
					When you're submitting an issue, generally they fall into a few categories:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Bug
 | 
					## Bug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
We need some information to figure out what's going wrong. At a minimum, you need to tell us:
 | 
					We need some information to figure out what's going wrong. At a minimum, you need to tell us:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (1) What's supposed to happen
 | 
					        (1) What's supposed to happen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (2) What actually happened
 | 
					        (2) What actually happened
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (3) Steps to reproduce
 | 
					        (3) Steps to reproduce
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Stack traces and other useful information that you find make a bug report more likely to be fixed.
 | 
					Stack traces and other useful information that you find make a bug report more likely to be fixed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Consult the template for a bug report if you don't know or have questions about how to format this.
 | 
					Consult the template for a bug report if you don't know or have questions about how to format this.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Documentation
 | 
					## Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Depending on how you go about it, this can be done as a [Pull Request](../contributing/pull-requests) instead of an issue. Generally, we need to know what was wrong, what you changed, and how it improved the documentation if it isn't obvious.
 | 
					Depending on how you go about it, this can be done as a [Pull Request](../contributing/pull-requests) instead of an issue. Generally, we need to know what was wrong, what you changed, and how it improved the documentation if it isn't obvious.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
We just need to know what's wrong. You should fill out a [PR](../contributing/pull-requests) if you know what should be there instead.
 | 
					We just need to know what's wrong. You should fill out a [PR](../contributing/pull-requests) if you know what should be there instead.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,77 +1,77 @@
 | 
				
			||||||
# Submitting Pull Requests
 | 
					# Submitting Pull Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When submitting a Pull Request, there's a few reasons to do so:
 | 
					When submitting a Pull Request, there's a few reasons to do so:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Documentation
 | 
					## Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If there's something to change with the documentation, you should follow a similar format to this example:
 | 
					If there's something to change with the documentation, you should follow a similar format to this example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
An example of an appropriate typo-fixing PR would be:
 | 
					An example of an appropriate typo-fixing PR would be:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
>**Brief Description of your Changes**
 | 
					>**Brief Description of your Changes**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>I fixed a couple of typos found in the /contributing/issues.md file.
 | 
					>I fixed a couple of typos found in the /contributing/issues.md file.
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Impact of your Changes**
 | 
					>**Impact of your Changes**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>- Documentation is more clear and readable for the users.
 | 
					>- Documentation is more clear and readable for the users.
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Tests Performed**
 | 
					>**Tests Performed**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>Ran `vitepress dev docs` and verified it was built successfully.
 | 
					>Ran `vitepress dev docs` and verified it was built successfully.
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Additional Comments**
 | 
					>**Additional Comments**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>[At Discretion]
 | 
					>[At Discretion]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Change in Behavior
 | 
					## Change in Behavior
 | 
				
			||||||
 | 
					
 | 
				
			||||||
An example of an appropriate PR that adds a new feature would be:
 | 
					An example of an appropriate PR that adds a new feature would be:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Brief Description of your Changes**
 | 
					>**Brief Description of your Changes**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>I added `jecs.best_function`, which gives everyone who uses the module an immediate boost in concurrent player counts. (this is a joke)
 | 
					>I added `jecs.best_function`, which gives everyone who uses the module an immediate boost in concurrent player counts. (this is a joke)
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Impact of your Changes**
 | 
					>**Impact of your Changes**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>- jecs functionality is extended to better fit the needs of the community [explain why].
 | 
					>- jecs functionality is extended to better fit the needs of the community [explain why].
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Tests Performed**
 | 
					>**Tests Performed**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>Added a few test cases to ensure the function runs as expected [link to changes].
 | 
					>Added a few test cases to ensure the function runs as expected [link to changes].
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Additional Comments**
 | 
					>**Additional Comments**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>[At Discretion]
 | 
					>[At Discretion]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Addons
 | 
					## Addons
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If you made something you think should be included into the [resources page](../../resources), let us know!
 | 
					If you made something you think should be included into the [resources page](../../resources), let us know!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
We have tons of examples of libraries and other tools which can be used in conjunction with jecs on this page.
 | 
					We have tons of examples of libraries and other tools which can be used in conjunction with jecs on this page.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
One example of a PR that would be accepted is:
 | 
					One example of a PR that would be accepted is:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
>**Brief Description of your Changes**
 | 
					>**Brief Description of your Changes**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>I added `jecs observers` to the addons page.
 | 
					>I added `jecs observers` to the addons page.
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Impact of your Changes**
 | 
					>**Impact of your Changes**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>- jecs observers are a different and important way of handling queries which benefit the users of jecs by [explain why your tool benefits users here]
 | 
					>- jecs observers are a different and important way of handling queries which benefit the users of jecs by [explain why your tool benefits users here]
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>- [talk about why you went with this design instead of maybe an alternative]
 | 
					>- [talk about why you went with this design instead of maybe an alternative]
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Tests Performed**
 | 
					>**Tests Performed**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
> I used this tool in conjunction with jecs and ensured it works as expected.
 | 
					> I used this tool in conjunction with jecs and ensured it works as expected.
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
> [If you wrote unit tests for your tool, mention it here.]
 | 
					> [If you wrote unit tests for your tool, mention it here.]
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>**Additional Comments**
 | 
					>**Additional Comments**
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
>[At Discretion]
 | 
					>[At Discretion]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Keep in mind the list on the addons page is *not* exhaustive. If you came up with a tool that doesn't fit into any of the categories listed, we still want to hear from you!
 | 
					Keep in mind the list on the addons page is *not* exhaustive. If you came up with a tool that doesn't fit into any of the categories listed, we still want to hear from you!
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										37
									
								
								jecs.luau
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								jecs.luau
									
									
									
									
									
								
							| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--!optimize 2
 | 
					--!optimize 2
 | 
				
			||||||
--!native
 | 
					--!native
 | 
				
			||||||
--!strict
 | 
					--!strict
 | 
				
			||||||
| 
						 | 
					@ -117,6 +118,7 @@ local ECS_ID_MASK =                          	    0b00
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local ECS_ENTITY_MASK =              bit32.lshift(1, 24)
 | 
					local ECS_ENTITY_MASK =              bit32.lshift(1, 24)
 | 
				
			||||||
local ECS_GENERATION_MASK =          bit32.lshift(1, 16)
 | 
					local ECS_GENERATION_MASK =          bit32.lshift(1, 16)
 | 
				
			||||||
 | 
					local ECS_PAIR_OFFSET = 						    2^48
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local NULL_ARRAY = table.freeze({}) :: Column
 | 
					local NULL_ARRAY = table.freeze({}) :: Column
 | 
				
			||||||
local NULL = newproxy(false)
 | 
					local NULL = newproxy(false)
 | 
				
			||||||
| 
						 | 
					@ -168,7 +170,6 @@ end
 | 
				
			||||||
local function ECS_COMBINE(id: number, generation: number): i53
 | 
					local function ECS_COMBINE(id: number, generation: number): i53
 | 
				
			||||||
	return id + (generation * ECS_ENTITY_MASK)
 | 
						return id + (generation * ECS_ENTITY_MASK)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
local ECS_PAIR_OFFSET = 2^48
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ECS_IS_PAIR(e: number): boolean
 | 
					local function ECS_IS_PAIR(e: number): boolean
 | 
				
			||||||
	return e > ECS_PAIR_OFFSET
 | 
						return e > ECS_PAIR_OFFSET
 | 
				
			||||||
| 
						 | 
					@ -2576,40 +2577,40 @@ export type World = {
 | 
				
			||||||
	component: <T>(self: World) -> Entity<T>,
 | 
						component: <T>(self: World) -> Entity<T>,
 | 
				
			||||||
	--- Gets the target of an relationship. For example, when a user calls
 | 
						--- Gets the target of an relationship. For example, when a user calls
 | 
				
			||||||
	--- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity.
 | 
						--- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity.
 | 
				
			||||||
	target: <T>(self: World, id: Entity, relation: Id<T>, index: number?) -> Entity?,
 | 
						target: <T, a>(self: World, id: Entity<T>, relation: Id<a>, index: number?) -> Entity?,
 | 
				
			||||||
	--- Deletes an entity and all it's related components and relationships.
 | 
						--- Deletes an entity and all it's related components and relationships.
 | 
				
			||||||
	delete: (self: World, id: Entity) -> (),
 | 
						delete: <T>(self: World, id: Entity<T>) -> (),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	--- Adds a component to the entity with no value
 | 
						--- Adds a component to the entity with no value
 | 
				
			||||||
	add: <T>(self: World, id: Entity, component: Id<T>) -> (),
 | 
						add: <T, a>(self: World, id: Entity<T>, component: Id<a>) -> (),
 | 
				
			||||||
	--- Assigns a value to a component on the given entity
 | 
						--- Assigns a value to a component on the given entity
 | 
				
			||||||
	set: <T>(self: World, id: Entity, component: Id<T>, data: T) -> (),
 | 
						set: <T, a>(self: World, id: Entity<T>, component: Id<a>, data: a) -> (),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cleanup: (self: World) -> (),
 | 
						cleanup: (self: World) -> (),
 | 
				
			||||||
	-- Clears an entity from the world
 | 
						-- Clears an entity from the world
 | 
				
			||||||
	clear: <T>(self: World, id: Id<T>) -> (),
 | 
						clear: <a>(self: World, id: Id<a>) -> (),
 | 
				
			||||||
	--- Removes a component from the given entity
 | 
						--- Removes a component from the given entity
 | 
				
			||||||
	remove: <T>(self: World, id: Entity, component: Id<T>) -> (),
 | 
						remove: <T, a>(self: World, id: Entity<T>, component: Id<a>) -> (),
 | 
				
			||||||
	--- Retrieves the value of up to 4 components. These values may be nil.
 | 
						--- Retrieves the value of up to 4 components. These values may be nil.
 | 
				
			||||||
	get: (<A>(self: World, id: Entity, Id<A>) -> A?)
 | 
						get: & (<T, a>(World, Entity<T>, Id<a>) -> a?)
 | 
				
			||||||
		& (<A, B>(self: World, id: Entity, Id<A>, Id<B>) -> (A?, B?))
 | 
							& (<T, a, b>(World, Entity<T>, Id<a>, Id<b>) -> (a?, b?))
 | 
				
			||||||
		& (<A, B, C>(self: World, id: Entity, Id<A>, Id<B>, Id<C>) -> (A?, B?, C?))
 | 
							& (<T, a, b, c>(World, Entity<T>, Id<a>, Id<b>, Id<c>) -> (a?, b?, c?))
 | 
				
			||||||
		& <A, B, C, D>(self: World, id: Entity, Id<A>, Id<B>, Id<C>, Id<D>) -> (A?, B?, C?, D?),
 | 
							& (<T, a, b, c, d>(World, Entity<T>, Id<a>, Id<b>, Id<c>, Id<d>) -> (a?, b?, c?, d?)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	--- Returns whether the entity has the ID.
 | 
						--- Returns whether the entity has the ID.
 | 
				
			||||||
	has: (<A>(World, Entity, A) -> boolean)
 | 
						has: (<T>(World, Entity<T>, Id) -> boolean)
 | 
				
			||||||
		& (<A, B>(World, Entity, A, B) -> boolean)
 | 
							& (<T>(World, Entity<T>, Id, Id) -> boolean)
 | 
				
			||||||
		& (<A, B, C>(World, Entity, A, B, C) -> boolean)
 | 
							& (<T>(World, Entity<T>, Id, Id, Id) -> boolean)
 | 
				
			||||||
		& <A, B, C, D>(World, Entity, A, B, C, D) -> boolean,
 | 
							& <T>(World, Entity<T>, Id, Id, Id, Id) -> boolean,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	--- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil.
 | 
						--- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil.
 | 
				
			||||||
	parent:(self: World, entity: Entity) -> Entity,
 | 
						parent: <T>(self: World, entity: Entity<T>) -> Entity,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	--- Checks if the world contains the given entity
 | 
						--- Checks if the world contains the given entity
 | 
				
			||||||
	contains:(self: World, entity: Entity) -> boolean,
 | 
						contains: <T>(self: World, entity: Entity<T>) -> boolean,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	--- Checks if the entity exists
 | 
						--- Checks if the entity exists
 | 
				
			||||||
	exists: (self: World, entity: Entity) -> boolean,
 | 
						exists: <T>(self: World, entity: Entity<T>) -> boolean,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	each: <T>(self: World, id: Id<T>) -> () -> Entity,
 | 
						each: <T>(self: World, id: Id<T>) -> () -> Entity,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										316
									
								
								test/lol.luau
									
									
									
									
									
								
							
							
						
						
									
										316
									
								
								test/lol.luau
									
									
									
									
									
								
							| 
						 | 
					@ -1,158 +1,158 @@
 | 
				
			||||||
local c = {
 | 
					local c = {
 | 
				
			||||||
	white_underline = function(s: any)
 | 
						white_underline = function(s: any)
 | 
				
			||||||
		return `\27[1;4m{s}\27[0m`
 | 
							return `\27[1;4m{s}\27[0m`
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	white = function(s: any)
 | 
						white = function(s: any)
 | 
				
			||||||
		return `\27[37;1m{s}\27[0m`
 | 
							return `\27[37;1m{s}\27[0m`
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	green = function(s: any)
 | 
						green = function(s: any)
 | 
				
			||||||
		return `\27[32;1m{s}\27[0m`
 | 
							return `\27[32;1m{s}\27[0m`
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	red = function(s: any)
 | 
						red = function(s: any)
 | 
				
			||||||
		return `\27[31;1m{s}\27[0m`
 | 
							return `\27[31;1m{s}\27[0m`
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	yellow = function(s: any)
 | 
						yellow = function(s: any)
 | 
				
			||||||
		return `\27[33;1m{s}\27[0m`
 | 
							return `\27[33;1m{s}\27[0m`
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	red_highlight = function(s: any)
 | 
						red_highlight = function(s: any)
 | 
				
			||||||
		return `\27[41;1;30m{s}\27[0m`
 | 
							return `\27[41;1;30m{s}\27[0m`
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	green_highlight = function(s: any)
 | 
						green_highlight = function(s: any)
 | 
				
			||||||
		return `\27[42;1;30m{s}\27[0m`
 | 
							return `\27[42;1;30m{s}\27[0m`
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gray = function(s: any)
 | 
						gray = function(s: any)
 | 
				
			||||||
		return `\27[30;1m{s}\27[0m`
 | 
							return `\27[30;1m{s}\27[0m`
 | 
				
			||||||
	end,
 | 
						end,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local ECS_PAIR_FLAG =                      0x8
 | 
					local ECS_PAIR_FLAG =                      0x8
 | 
				
			||||||
local ECS_ID_FLAGS_MASK =                 0x10
 | 
					local ECS_ID_FLAGS_MASK =                 0x10
 | 
				
			||||||
local ECS_ENTITY_MASK =     bit32.lshift(1, 24)
 | 
					local ECS_ENTITY_MASK =     bit32.lshift(1, 24)
 | 
				
			||||||
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
 | 
					local ECS_GENERATION_MASK = bit32.lshift(1, 16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type i53 = number
 | 
					type i53 = number
 | 
				
			||||||
type i24 = number
 | 
					type i24 = number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ECS_ENTITY_T_LO(e: i53): i24
 | 
					local function ECS_ENTITY_T_LO(e: i53): i24
 | 
				
			||||||
	return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
 | 
						return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ECS_GENERATION(e: i53): i24
 | 
					local function ECS_GENERATION(e: i53): i24
 | 
				
			||||||
	return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
 | 
						return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local ECS_ID = ECS_ENTITY_T_LO
 | 
					local ECS_ID = ECS_ENTITY_T_LO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ECS_COMBINE(source: number, target: number): i53
 | 
					local function ECS_COMBINE(source: number, target: number): i53
 | 
				
			||||||
	return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
 | 
						return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function ECS_GENERATION_INC(e: i53)
 | 
					local function ECS_GENERATION_INC(e: i53)
 | 
				
			||||||
	if e > ECS_ENTITY_MASK then
 | 
						if e > ECS_ENTITY_MASK then
 | 
				
			||||||
		local flags = e // ECS_ID_FLAGS_MASK
 | 
							local flags = e // ECS_ID_FLAGS_MASK
 | 
				
			||||||
		local id = flags // ECS_ENTITY_MASK
 | 
							local id = flags // ECS_ENTITY_MASK
 | 
				
			||||||
		local generation = flags % ECS_GENERATION_MASK
 | 
							local generation = flags % ECS_GENERATION_MASK
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local next_gen = generation + 1
 | 
							local next_gen = generation + 1
 | 
				
			||||||
		if next_gen > ECS_GENERATION_MASK then
 | 
							if next_gen > ECS_GENERATION_MASK then
 | 
				
			||||||
			return id
 | 
								return id
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return ECS_COMBINE(id, next_gen) + flags
 | 
							return ECS_COMBINE(id, next_gen) + flags
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	return ECS_COMBINE(e, 1)
 | 
						return ECS_COMBINE(e, 1)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function bl()
 | 
					local function bl()
 | 
				
			||||||
	print("")
 | 
						print("")
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function pe(e)
 | 
					local function pe(e)
 | 
				
			||||||
	local gen = ECS_GENERATION(e)
 | 
						local gen = ECS_GENERATION(e)
 | 
				
			||||||
	return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
 | 
						return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function dprint(tbl: {  [number]: number })
 | 
					local function dprint(tbl: {  [number]: number })
 | 
				
			||||||
	bl()
 | 
						bl()
 | 
				
			||||||
	print("--------")
 | 
						print("--------")
 | 
				
			||||||
	for i, e in tbl do
 | 
						for i, e in tbl do
 | 
				
			||||||
		print("| "..pe(e).." |")
 | 
							print("| "..pe(e).." |")
 | 
				
			||||||
		print("--------")
 | 
							print("--------")
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	bl()
 | 
						bl()
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local max_id = 0
 | 
					local max_id = 0
 | 
				
			||||||
local alive_count = 0
 | 
					local alive_count = 0
 | 
				
			||||||
local dense = {}
 | 
					local dense = {}
 | 
				
			||||||
local sparse = {}
 | 
					local sparse = {}
 | 
				
			||||||
local function alloc()
 | 
					local function alloc()
 | 
				
			||||||
	if alive_count ~= #dense then
 | 
						if alive_count ~= #dense then
 | 
				
			||||||
		alive_count += 1
 | 
							alive_count += 1
 | 
				
			||||||
		print("*recycled", pe(dense[alive_count]))
 | 
							print("*recycled", pe(dense[alive_count]))
 | 
				
			||||||
		return dense[alive_count]
 | 
							return dense[alive_count]
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	max_id += 1
 | 
						max_id += 1
 | 
				
			||||||
	local id = max_id
 | 
						local id = max_id
 | 
				
			||||||
	alive_count += 1
 | 
						alive_count += 1
 | 
				
			||||||
	dense[alive_count] = id
 | 
						dense[alive_count] = id
 | 
				
			||||||
	sparse[id] = {
 | 
						sparse[id] = {
 | 
				
			||||||
		dense = alive_count
 | 
							dense = alive_count
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	print("*allocated", pe(id))
 | 
						print("*allocated", pe(id))
 | 
				
			||||||
	return id
 | 
						return id
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function remove(entity)
 | 
					local function remove(entity)
 | 
				
			||||||
	local id = ECS_ID(entity)
 | 
						local id = ECS_ID(entity)
 | 
				
			||||||
	local r = sparse[id]
 | 
						local r = sparse[id]
 | 
				
			||||||
	local index_of_deleted_entity = r.dense
 | 
						local index_of_deleted_entity = r.dense
 | 
				
			||||||
	local last_entity_alive_at_index = alive_count -- last entity alive
 | 
						local last_entity_alive_at_index = alive_count -- last entity alive
 | 
				
			||||||
	alive_count -= 1
 | 
						alive_count -= 1
 | 
				
			||||||
	local last_alive_entity = dense[last_entity_alive_at_index]
 | 
						local last_alive_entity = dense[last_entity_alive_at_index]
 | 
				
			||||||
	local r_swap = sparse[ECS_ID(last_alive_entity)]
 | 
						local r_swap = sparse[ECS_ID(last_alive_entity)]
 | 
				
			||||||
	r_swap.dense = r.dense
 | 
						r_swap.dense = r.dense
 | 
				
			||||||
	r.dense = last_entity_alive_at_index
 | 
						r.dense = last_entity_alive_at_index
 | 
				
			||||||
	dense[index_of_deleted_entity] = last_alive_entity
 | 
						dense[index_of_deleted_entity] = last_alive_entity
 | 
				
			||||||
	dense[last_entity_alive_at_index] = ECS_GENERATION_INC(entity)
 | 
						dense[last_entity_alive_at_index] = ECS_GENERATION_INC(entity)
 | 
				
			||||||
	print("*dellocated", pe(id))
 | 
						print("*dellocated", pe(id))
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function alive(e)
 | 
					local function alive(e)
 | 
				
			||||||
	local r = sparse[ECS_ID(e)]
 | 
						local r = sparse[ECS_ID(e)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return dense[r.dense] == e
 | 
						return dense[r.dense] == e
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function pa(e)
 | 
					local function pa(e)
 | 
				
			||||||
	print(`{pe(e)} is {if alive(e) then "alive" else "not alive"}`)
 | 
						print(`{pe(e)} is {if alive(e) then "alive" else "not alive"}`)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local tprint = require("@testkit").print
 | 
					local tprint = require("@testkit").print
 | 
				
			||||||
local e1v0 = alloc()
 | 
					local e1v0 = alloc()
 | 
				
			||||||
local e2v0 = alloc()
 | 
					local e2v0 = alloc()
 | 
				
			||||||
local e3v0 = alloc()
 | 
					local e3v0 = alloc()
 | 
				
			||||||
local e4v0 = alloc()
 | 
					local e4v0 = alloc()
 | 
				
			||||||
local e5v0 = alloc()
 | 
					local e5v0 = alloc()
 | 
				
			||||||
pa(e1v0)
 | 
					pa(e1v0)
 | 
				
			||||||
pa(e4v0)
 | 
					pa(e4v0)
 | 
				
			||||||
remove(e5v0)
 | 
					remove(e5v0)
 | 
				
			||||||
pa(e5v0)
 | 
					pa(e5v0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local e5v1 = alloc()
 | 
					local e5v1 = alloc()
 | 
				
			||||||
pa(e5v0)
 | 
					pa(e5v0)
 | 
				
			||||||
pa(e5v1)
 | 
					pa(e5v1)
 | 
				
			||||||
pa(e2v0)
 | 
					pa(e2v0)
 | 
				
			||||||
print(ECS_ID(e2v0))
 | 
					print(ECS_ID(e2v0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dprint(dense)
 | 
					dprint(dense)
 | 
				
			||||||
remove(e2v0)
 | 
					remove(e2v0)
 | 
				
			||||||
dprint(dense)
 | 
					dprint(dense)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,122 +1,122 @@
 | 
				
			||||||
local RunService = game:GetService("RunService")
 | 
					local RunService = game:GetService("RunService")
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
_G.__JECS_HI_COMPONENT_ID = 300
 | 
					_G.__JECS_HI_COMPONENT_ID = 300
 | 
				
			||||||
local ecs = require(ReplicatedStorage.ecs)
 | 
					local ecs = require(ReplicatedStorage.ecs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- 500 entities
 | 
					-- 500 entities
 | 
				
			||||||
-- 2-30 components on each entity
 | 
					-- 2-30 components on each entity
 | 
				
			||||||
-- 300 unique components
 | 
					-- 300 unique components
 | 
				
			||||||
-- 200 systems
 | 
					-- 200 systems
 | 
				
			||||||
-- 1-10 components to query per system
 | 
					-- 1-10 components to query per system
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local startTime = os.clock()
 | 
					local startTime = os.clock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local world = ecs.World.new()
 | 
					local world = ecs.World.new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local components = {}
 | 
					local components = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for i = 1, 300 do -- 300 components
 | 
					for i = 1, 300 do -- 300 components
 | 
				
			||||||
	components[i] = world:component()
 | 
						components[i] = world:component()
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local archetypes = {}
 | 
					local archetypes = {}
 | 
				
			||||||
for i = 1, 50 do -- 50 archetypes
 | 
					for i = 1, 50 do -- 50 archetypes
 | 
				
			||||||
	local archetype = {}
 | 
						local archetype = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _ = 1, math.random(2, 30) do
 | 
						for _ = 1, math.random(2, 30) do
 | 
				
			||||||
		local componentId = math.random(1, #components)
 | 
							local componentId = math.random(1, #components)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		table.insert(archetype, components[componentId])
 | 
							table.insert(archetype, components[componentId])
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	archetypes[i] = archetype
 | 
						archetypes[i] = archetype
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for _ = 1, 1000 do -- 1000 entities in the world
 | 
					for _ = 1, 1000 do -- 1000 entities in the world
 | 
				
			||||||
	local componentsToAdd = {}
 | 
						local componentsToAdd = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local archetypeId = math.random(1, #archetypes)
 | 
						local archetypeId = math.random(1, #archetypes)
 | 
				
			||||||
	local e = world:entity()
 | 
						local e = world:entity()
 | 
				
			||||||
	for _, component in ipairs(archetypes[archetypeId]) do
 | 
						for _, component in ipairs(archetypes[archetypeId]) do
 | 
				
			||||||
		world:set(e, component, {
 | 
							world:set(e, component, {
 | 
				
			||||||
			DummyData = math.random(1, 5000),
 | 
								DummyData = math.random(1, 5000),
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function values(t)
 | 
					local function values(t)
 | 
				
			||||||
	local array = {}
 | 
						local array = {}
 | 
				
			||||||
	for _, v in t do
 | 
						for _, v in t do
 | 
				
			||||||
		table.insert(array, v)
 | 
							table.insert(array, v)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	return array
 | 
						return array
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local contiguousComponents = values(components)
 | 
					local contiguousComponents = values(components)
 | 
				
			||||||
local systemComponentsToQuery = {}
 | 
					local systemComponentsToQuery = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for _ = 1, 200 do -- 200 systems
 | 
					for _ = 1, 200 do -- 200 systems
 | 
				
			||||||
	local numComponentsToQuery = math.random(1, 10)
 | 
						local numComponentsToQuery = math.random(1, 10)
 | 
				
			||||||
	local componentsToQuery = {}
 | 
						local componentsToQuery = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _ = 1, numComponentsToQuery do
 | 
						for _ = 1, numComponentsToQuery do
 | 
				
			||||||
		table.insert(componentsToQuery, contiguousComponents[math.random(1, #contiguousComponents)])
 | 
							table.insert(componentsToQuery, contiguousComponents[math.random(1, #contiguousComponents)])
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	table.insert(systemComponentsToQuery, componentsToQuery)
 | 
						table.insert(systemComponentsToQuery, componentsToQuery)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local worldCreateTime = os.clock() - startTime
 | 
					local worldCreateTime = os.clock() - startTime
 | 
				
			||||||
local results = {}
 | 
					local results = {}
 | 
				
			||||||
startTime = os.clock()
 | 
					startTime = os.clock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RunService.Heartbeat:Connect(function()
 | 
					RunService.Heartbeat:Connect(function()
 | 
				
			||||||
	local added = 0
 | 
						local added = 0
 | 
				
			||||||
	local systemStartTime = os.clock()
 | 
						local systemStartTime = os.clock()
 | 
				
			||||||
	debug.profilebegin("systems")
 | 
						debug.profilebegin("systems")
 | 
				
			||||||
	for _, componentsToQuery in ipairs(systemComponentsToQuery) do
 | 
						for _, componentsToQuery in ipairs(systemComponentsToQuery) do
 | 
				
			||||||
		debug.profilebegin("system")
 | 
							debug.profilebegin("system")
 | 
				
			||||||
		for entityId, firstComponent in world:query(unpack(componentsToQuery)) do
 | 
							for entityId, firstComponent in world:query(unpack(componentsToQuery)) do
 | 
				
			||||||
			world:set(
 | 
								world:set(
 | 
				
			||||||
				entityId,
 | 
									entityId,
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					DummyData = firstComponent.DummyData + 1,
 | 
										DummyData = firstComponent.DummyData + 1,
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
			added += 1
 | 
								added += 1
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		debug.profileend()
 | 
							debug.profileend()
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	debug.profileend()
 | 
						debug.profileend()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if os.clock() - startTime < 4 then
 | 
						if os.clock() - startTime < 4 then
 | 
				
			||||||
		-- discard first 4 seconds
 | 
							-- discard first 4 seconds
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if results == nil then
 | 
						if results == nil then
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	elseif #results < 1000 then
 | 
						elseif #results < 1000 then
 | 
				
			||||||
		table.insert(results, os.clock() - systemStartTime)
 | 
							table.insert(results, os.clock() - systemStartTime)
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
		print("added", added)
 | 
							print("added", added)
 | 
				
			||||||
		print("World created in", worldCreateTime * 1000, "ms")
 | 
							print("World created in", worldCreateTime * 1000, "ms")
 | 
				
			||||||
		local sum = 0
 | 
							local sum = 0
 | 
				
			||||||
		for _, result in ipairs(results) do
 | 
							for _, result in ipairs(results) do
 | 
				
			||||||
			sum += result
 | 
								sum += result
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		print(("Average frame time: %fms"):format((sum / #results) * 1000))
 | 
							print(("Average frame time: %fms"):format((sum / #results) * 1000))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		results = nil
 | 
							results = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local n = #world.archetypes
 | 
							local n = #world.archetypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		print(
 | 
							print(
 | 
				
			||||||
			("X entities\n%d components\n%d systems\n%d archetypes"):format(
 | 
								("X entities\n%d components\n%d systems\n%d archetypes"):format(
 | 
				
			||||||
				#components,
 | 
									#components,
 | 
				
			||||||
				#systemComponentsToQuery,
 | 
									#systemComponentsToQuery,
 | 
				
			||||||
				n
 | 
									n
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
end)
 | 
					end)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,24 @@
 | 
				
			||||||
local jecs = require("@jecs")
 | 
					local jecs = require("@jecs")
 | 
				
			||||||
local pair = jecs.pair
 | 
					local pair = jecs.pair
 | 
				
			||||||
local ChildOf = jecs.ChildOf
 | 
					local ChildOf = jecs.ChildOf
 | 
				
			||||||
local lifetime_tracker_add = require("@tools/lifetime_tracker")
 | 
					local lifetime_tracker_add = require("@tools/lifetime_tracker")
 | 
				
			||||||
local world = lifetime_tracker_add(jecs.world(), {padding_enabled=false})
 | 
					local world = lifetime_tracker_add(jecs.world(), {padding_enabled=false})
 | 
				
			||||||
local FriendsWith = world:component()
 | 
					local FriendsWith = world:component()
 | 
				
			||||||
world:print_snapshot()
 | 
					world:print_snapshot()
 | 
				
			||||||
local e1 = world:entity()
 | 
					local e1 = world:entity()
 | 
				
			||||||
local e2 = world:entity()
 | 
					local e2 = world:entity()
 | 
				
			||||||
world:delete(e2)
 | 
					world:delete(e2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
world:print_snapshot()
 | 
					world:print_snapshot()
 | 
				
			||||||
local e3 = world:entity()
 | 
					local e3 = world:entity()
 | 
				
			||||||
world:add(e3, pair(ChildOf, e1))
 | 
					world:add(e3, pair(ChildOf, e1))
 | 
				
			||||||
local e4 = world:entity()
 | 
					local e4 = world:entity()
 | 
				
			||||||
world:add(e4, pair(FriendsWith, e3))
 | 
					world:add(e4, pair(FriendsWith, e3))
 | 
				
			||||||
world:print_snapshot()
 | 
					world:print_snapshot()
 | 
				
			||||||
world:delete(e1)
 | 
					world:delete(e1)
 | 
				
			||||||
world:delete(e3)
 | 
					world:delete(e3)
 | 
				
			||||||
world:print_snapshot()
 | 
					world:print_snapshot()
 | 
				
			||||||
world:print_entity_index()
 | 
					world:print_entity_index()
 | 
				
			||||||
world:entity()
 | 
					world:entity()
 | 
				
			||||||
world:entity()
 | 
					world:entity()
 | 
				
			||||||
world:print_snapshot()
 | 
					world:print_snapshot()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,153 +1,153 @@
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LCOV_FILE = "coverage.out"
 | 
					LCOV_FILE = "coverage.out"
 | 
				
			||||||
OUTPUT_DIR = "coverage"
 | 
					OUTPUT_DIR = "coverage"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
 | 
					os.makedirs(OUTPUT_DIR, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_lcov(content):
 | 
					def parse_lcov(content):
 | 
				
			||||||
    """Parses LCOV data from a single string."""
 | 
					    """Parses LCOV data from a single string."""
 | 
				
			||||||
    files = {}
 | 
					    files = {}
 | 
				
			||||||
    current_file = None
 | 
					    current_file = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for line in content.splitlines():
 | 
					    for line in content.splitlines():
 | 
				
			||||||
        if line.startswith("SF:"):
 | 
					        if line.startswith("SF:"):
 | 
				
			||||||
            current_file = line[3:].strip()
 | 
					            current_file = line[3:].strip()
 | 
				
			||||||
            files[current_file] = {"coverage": {}, "functions": []}
 | 
					            files[current_file] = {"coverage": {}, "functions": []}
 | 
				
			||||||
        elif line.startswith("DA:") and current_file:
 | 
					        elif line.startswith("DA:") and current_file:
 | 
				
			||||||
            parts = line[3:].split(",")
 | 
					            parts = line[3:].split(",")
 | 
				
			||||||
            line_num = int(parts[0])
 | 
					            line_num = int(parts[0])
 | 
				
			||||||
            execution_count = int(parts[1])
 | 
					            execution_count = int(parts[1])
 | 
				
			||||||
            files[current_file]["coverage"][line_num] = execution_count
 | 
					            files[current_file]["coverage"][line_num] = execution_count
 | 
				
			||||||
        elif line.startswith("FN:") and current_file:
 | 
					        elif line.startswith("FN:") and current_file:
 | 
				
			||||||
            parts = line[3:].split(",")
 | 
					            parts = line[3:].split(",")
 | 
				
			||||||
            line_num = int(parts[0])
 | 
					            line_num = int(parts[0])
 | 
				
			||||||
            function_name = parts[1].strip()
 | 
					            function_name = parts[1].strip()
 | 
				
			||||||
            files[current_file]["functions"].append({"name": function_name, "line": line_num, "hits": 0})
 | 
					            files[current_file]["functions"].append({"name": function_name, "line": line_num, "hits": 0})
 | 
				
			||||||
        elif line.startswith("FNDA:") and current_file:
 | 
					        elif line.startswith("FNDA:") and current_file:
 | 
				
			||||||
            parts = line[5:].split(",")
 | 
					            parts = line[5:].split(",")
 | 
				
			||||||
            hit_count = int(parts[0])
 | 
					            hit_count = int(parts[0])
 | 
				
			||||||
            function_name = parts[1].strip()
 | 
					            function_name = parts[1].strip()
 | 
				
			||||||
            for func in files[current_file]["functions"]:
 | 
					            for func in files[current_file]["functions"]:
 | 
				
			||||||
                if func["name"] == function_name:
 | 
					                if func["name"] == function_name:
 | 
				
			||||||
                    func["hits"] = hit_count
 | 
					                    func["hits"] = hit_count
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return files
 | 
					    return files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def read_source_file(filepath):
 | 
					def read_source_file(filepath):
 | 
				
			||||||
    """Reads source file content if available."""
 | 
					    """Reads source file content if available."""
 | 
				
			||||||
    if not os.path.exists(filepath):
 | 
					    if not os.path.exists(filepath):
 | 
				
			||||||
        return []
 | 
					        return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(filepath, "r", encoding="utf-8") as f:
 | 
					    with open(filepath, "r", encoding="utf-8") as f:
 | 
				
			||||||
        return f.readlines()
 | 
					        return f.readlines()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_file_html(filepath, coverage_data, functions_data):
 | 
					def generate_file_html(filepath, coverage_data, functions_data):
 | 
				
			||||||
    """Generates an HTML file for a specific source file."""
 | 
					    """Generates an HTML file for a specific source file."""
 | 
				
			||||||
    filename = os.path.basename(filepath)
 | 
					    filename = os.path.basename(filepath)
 | 
				
			||||||
    source_code = read_source_file(filepath)
 | 
					    source_code = read_source_file(filepath)
 | 
				
			||||||
    html_path = os.path.join(OUTPUT_DIR, f"{filename}.html")
 | 
					    html_path = os.path.join(OUTPUT_DIR, f"{filename}.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    total_hits = sum(func["hits"] for func in functions_data)
 | 
					    total_hits = sum(func["hits"] for func in functions_data)
 | 
				
			||||||
    max_hits = max((func["hits"] for func in functions_data), default=0)
 | 
					    max_hits = max((func["hits"] for func in functions_data), default=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    total_functions = len(functions_data)
 | 
					    total_functions = len(functions_data)
 | 
				
			||||||
    covered_functions = sum(1 for func in functions_data if func["hits"] > 0)
 | 
					    covered_functions = sum(1 for func in functions_data if func["hits"] > 0)
 | 
				
			||||||
    function_coverage_percent = (covered_functions / total_functions * 100) if total_functions > 0 else 0
 | 
					    function_coverage_percent = (covered_functions / total_functions * 100) if total_functions > 0 else 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lines = [
 | 
					    lines = [
 | 
				
			||||||
        "<html><head>",
 | 
					        "<html><head>",
 | 
				
			||||||
        '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css">',
 | 
					        '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css">',
 | 
				
			||||||
        '<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>',
 | 
					        '<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>',
 | 
				
			||||||
        "<style>",
 | 
					        "<style>",
 | 
				
			||||||
        "body { font-family: monospace; text-align: center; }",
 | 
					        "body { font-family: monospace; text-align: center; }",
 | 
				
			||||||
        "#funcTable table { margin: 0 auto; width: auto; max-width: 300px; font-size: 14px; border-collapse: collapse; }",
 | 
					        "#funcTable table { margin: 0 auto; width: auto; max-width: 300px; font-size: 14px; border-collapse: collapse; }",
 | 
				
			||||||
        "#funcTable th, #funcTable td { padding: 2px 6px; text-align: left; white-space: nowrap; }",
 | 
					        "#funcTable th, #funcTable td { padding: 2px 6px; text-align: left; white-space: nowrap; }",
 | 
				
			||||||
        "#funcTable th { background-color: #ddd; }",
 | 
					        "#funcTable th { background-color: #ddd; }",
 | 
				
			||||||
        "#funcTable td:nth-child(2) { text-align: right; min-width: 50px; }",
 | 
					        "#funcTable td:nth-child(2) { text-align: right; min-width: 50px; }",
 | 
				
			||||||
        ".zero-hits { background-color: #fcc; font-weight: bold; color: red; }",
 | 
					        ".zero-hits { background-color: #fcc; font-weight: bold; color: red; }",
 | 
				
			||||||
        ".nonzero-hits { color: green; font-weight: bold; }",
 | 
					        ".nonzero-hits { color: green; font-weight: bold; }",
 | 
				
			||||||
        ".low-hits { background-color: #ffe6b3; }",
 | 
					        ".low-hits { background-color: #ffe6b3; }",
 | 
				
			||||||
        ".high-hits { background-color: #cfc; }",
 | 
					        ".high-hits { background-color: #cfc; }",
 | 
				
			||||||
        ".source-code-table { margin-left: 10px; }"
 | 
					        ".source-code-table { margin-left: 10px; }"
 | 
				
			||||||
        "th, td { padding: 0px; font-size: 12px; }",
 | 
					        "th, td { padding: 0px; font-size: 12px; }",
 | 
				
			||||||
        "table.table { font-size: 14px; border-collapse: collapse; }",
 | 
					        "table.table { font-size: 14px; border-collapse: collapse; }",
 | 
				
			||||||
        "table.table th, table.table td { padding: 1px; font-size: 12px; line-height: 1.2; }",
 | 
					        "table.table th, table.table td { padding: 1px; font-size: 12px; line-height: 1.2; }",
 | 
				
			||||||
        "table.table tr { height: auto; }",
 | 
					        "table.table tr { height: auto; }",
 | 
				
			||||||
        "</style></head><body>",
 | 
					        "</style></head><body>",
 | 
				
			||||||
        f'<h1 class="text-center">{filename} Coverage</h1>',
 | 
					        f'<h1 class="text-center">{filename} Coverage</h1>',
 | 
				
			||||||
        f'<h2>Total Execution Hits: {total_hits}</h2>',
 | 
					        f'<h2>Total Execution Hits: {total_hits}</h2>',
 | 
				
			||||||
        f'<h2>Function Coverage Overview: {function_coverage_percent:.2f}%</h2>',
 | 
					        f'<h2>Function Coverage Overview: {function_coverage_percent:.2f}%</h2>',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '<button class="btn btn-primary mb-2" type="button" data-bs-toggle="collapse" data-bs-target="#funcTable">'
 | 
					        '<button class="btn btn-primary mb-2" type="button" data-bs-toggle="collapse" data-bs-target="#funcTable">'
 | 
				
			||||||
        'Toggle Function Coverage</button>',
 | 
					        'Toggle Function Coverage</button>',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '<div class="collapse show" id="funcTable">',
 | 
					        '<div class="collapse show" id="funcTable">',
 | 
				
			||||||
        '<h2>Function Coverage:</h2><table class="table table-bordered"><thead><tr><th>Function</th><th>Hits</th></tr></thead><tbody>'
 | 
					        '<h2>Function Coverage:</h2><table class="table table-bordered"><thead><tr><th>Function</th><th>Hits</th></tr></thead><tbody>'
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    longest_name = max((len(func["name"]) for func in functions_data), default=0)
 | 
					    longest_name = max((len(func["name"]) for func in functions_data), default=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for func in functions_data:
 | 
					    for func in functions_data:
 | 
				
			||||||
        hit_color = "red" if func["hits"] == 0 else "green"
 | 
					        hit_color = "red" if func["hits"] == 0 else "green"
 | 
				
			||||||
        lines.append(
 | 
					        lines.append(
 | 
				
			||||||
            f'<tr><td style="padding: 1px; min-width: {longest_name}ch;">{func["name"]}</td>'
 | 
					            f'<tr><td style="padding: 1px; min-width: {longest_name}ch;">{func["name"]}</td>'
 | 
				
			||||||
            f'<td style="padding: 1px; color: {hit_color}; font-weight: bold;">{func["hits"]}</td></tr>'
 | 
					            f'<td style="padding: 1px; color: {hit_color}; font-weight: bold;">{func["hits"]}</td></tr>'
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lines.append('</tbody></table></div>')  # Close collapsible div
 | 
					    lines.append('</tbody></table></div>')  # Close collapsible div
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lines.append('<h2>Source Code:</h2><table class="table table-bordered source-code-table "><thead><tr><th>Line</th><th>Hits</th><th>Code</th></tr></thead><tbody>')
 | 
					    lines.append('<h2>Source Code:</h2><table class="table table-bordered source-code-table "><thead><tr><th>Line</th><th>Hits</th><th>Code</th></tr></thead><tbody>')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for i, line in enumerate(source_code, start=1):
 | 
					    for i, line in enumerate(source_code, start=1):
 | 
				
			||||||
        stripped_line = line.strip()
 | 
					        stripped_line = line.strip()
 | 
				
			||||||
        class_name = "text-muted"
 | 
					        class_name = "text-muted"
 | 
				
			||||||
        if not stripped_line or stripped_line.startswith("end") or stripped_line.startswith("--"):
 | 
					        if not stripped_line or stripped_line.startswith("end") or stripped_line.startswith("--"):
 | 
				
			||||||
            count_display = "<span class='text-muted'>N/A</span>"
 | 
					            count_display = "<span class='text-muted'>N/A</span>"
 | 
				
			||||||
            lines.append(f'<tr><td>{i}</td><td>{count_display}</td><td>{line.strip()}</td>></tr>')
 | 
					            lines.append(f'<tr><td>{i}</td><td>{count_display}</td><td>{line.strip()}</td>></tr>')
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            count = coverage_data.get(i, 0)
 | 
					            count = coverage_data.get(i, 0)
 | 
				
			||||||
            class_name = "zero-hits" if count == 0 else "low-hits" if count < max_hits * 0.3 else "high-hits"
 | 
					            class_name = "zero-hits" if count == 0 else "low-hits" if count < max_hits * 0.3 else "high-hits"
 | 
				
			||||||
            count_display = f'{count}'
 | 
					            count_display = f'{count}'
 | 
				
			||||||
            marked_text = f'<span class={class_name}>{line.strip()}</span>'
 | 
					            marked_text = f'<span class={class_name}>{line.strip()}</span>'
 | 
				
			||||||
            lines.append(f'<tr><td>{i}</td><td>{count_display}</td><td>{marked_text}</td></tr>')
 | 
					            lines.append(f'<tr><td>{i}</td><td>{count_display}</td><td>{marked_text}</td></tr>')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lines.append("</tbody></table></body></html>")
 | 
					    lines.append("</tbody></table></body></html>")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(html_path, "w", encoding="utf-8") as f:
 | 
					    with open(html_path, "w", encoding="utf-8") as f:
 | 
				
			||||||
        f.write("\n".join(lines))
 | 
					        f.write("\n".join(lines))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_index(files):
 | 
					def generate_index(files):
 | 
				
			||||||
    """Generates an index.html summarizing the coverage."""
 | 
					    """Generates an index.html summarizing the coverage."""
 | 
				
			||||||
    index_html = [
 | 
					    index_html = [
 | 
				
			||||||
        "<html><head>",
 | 
					        "<html><head>",
 | 
				
			||||||
        '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css">',
 | 
					        '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css">',
 | 
				
			||||||
        "</head><body>",
 | 
					        "</head><body>",
 | 
				
			||||||
        '<h1 class="text-center">Coverage Report</h1>',
 | 
					        '<h1 class="text-center">Coverage Report</h1>',
 | 
				
			||||||
        '<table class="table table-striped table-bordered"><thead><tr><th>File</th><th>Total Hits</th><th>Functions</th></tr></thead><tbody>'
 | 
					        '<table class="table table-striped table-bordered"><thead><tr><th>File</th><th>Total Hits</th><th>Functions</th></tr></thead><tbody>'
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for filepath, data in files.items():
 | 
					    for filepath, data in files.items():
 | 
				
			||||||
        filename = os.path.basename(filepath)
 | 
					        filename = os.path.basename(filepath)
 | 
				
			||||||
        total_hits = sum(func["hits"] for func in data["functions"])
 | 
					        total_hits = sum(func["hits"] for func in data["functions"])
 | 
				
			||||||
        total_functions = len(data["functions"])
 | 
					        total_functions = len(data["functions"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        index_html.append(f'<tr><td><a href="{filename}.html">{filename}</a></td><td>{total_hits}</td><td>{total_functions}</td></tr>')
 | 
					        index_html.append(f'<tr><td><a href="{filename}.html">{filename}</a></td><td>{total_hits}</td><td>{total_functions}</td></tr>')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    index_html.append("</tbody></table></body></html>")
 | 
					    index_html.append("</tbody></table></body></html>")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(os.path.join(OUTPUT_DIR, "index.html"), "w", encoding="utf-8") as f:
 | 
					    with open(os.path.join(OUTPUT_DIR, "index.html"), "w", encoding="utf-8") as f:
 | 
				
			||||||
        f.write("\n".join(index_html))
 | 
					        f.write("\n".join(index_html))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
with open(LCOV_FILE, "r", encoding="utf-8") as f:
 | 
					with open(LCOV_FILE, "r", encoding="utf-8") as f:
 | 
				
			||||||
    lcov_content = f.read()
 | 
					    lcov_content = f.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
files_data = parse_lcov(lcov_content)
 | 
					files_data = parse_lcov(lcov_content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for file_path, data in files_data.items():
 | 
					for file_path, data in files_data.items():
 | 
				
			||||||
    generate_file_html(file_path, data["coverage"], data["functions"])
 | 
					    generate_file_html(file_path, data["coverage"], data["functions"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
generate_index(files_data)
 | 
					generate_index(files_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
print(f"Coverage report generated in {OUTPUT_DIR}/index.html")
 | 
					print(f"Coverage report generated in {OUTPUT_DIR}/index.html")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue