Fix line endings on demo (#232)
Some checks failed
analysis / Run Luau Analyze (push) Has been cancelled
deploy-docs / build (push) Has been cancelled
publish-npm / publish (push) Has been cancelled
unit-testing / Run Luau Tests (push) Has been cancelled
deploy-docs / Deploy (push) Has been cancelled

This commit is contained in:
Marcus 2025-06-01 16:19:48 +02:00 committed by GitHub
parent 3be946db0c
commit 803616a005
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1128 additions and 1128 deletions

View file

@ -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,
}, },
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 }?
} }
}>, }>,
} }

View file

@ -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,
} }
} }

View file

@ -1,86 +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, id) local function ecs_map_get(world, id)
local deserialised_id = client_ids[id] local deserialised_id = client_ids[id]
if not deserialised_id then if not deserialised_id then
if world:has(id, jecs.Name) then if world:has(id, jecs.Name) then
deserialised_id = world:entity(id) deserialised_id = world:entity(id)
else else
deserialised_id = world:entity() deserialised_id = world:entity()
end end
client_ids[id] = deserialised_id client_ids[id] = deserialised_id
end end
-- local deserialised_id = client_ids[id] -- local deserialised_id = client_ids[id]
-- if not deserialised_id then -- if not deserialised_id then
-- if world:has(id, jecs.Name) then -- if world:has(id, jecs.Name) then
-- deserialised_id = world:entity(id) -- deserialised_id = world:entity(id)
-- else -- else
-- if world:exists(id) then -- if world:exists(id) then
-- deserialised_id = world:entity() -- deserialised_id = world:entity()
-- else -- else
-- deserialised_id = world:entity(id) -- deserialised_id = world:entity(id)
-- end -- end
-- end -- end
-- client_ids[id] = deserialised_id -- client_ids[id] = deserialised_id
-- end -- end
return deserialised_id return deserialised_id
end end
local function ecs_make_alive_id(world, id) local function ecs_make_alive_id(world, id)
local rel = jecs.ECS_PAIR_FIRST(id) local rel = jecs.ECS_PAIR_FIRST(id)
local tgt = jecs.ECS_PAIR_SECOND(id) local tgt = jecs.ECS_PAIR_SECOND(id)
rel = ecs_map_get(world, rel) rel = ecs_map_get(world, rel)
tgt = ecs_map_get(world, tgt) tgt = ecs_map_get(world, tgt)
return jecs.pair(rel, tgt) return jecs.pair(rel, tgt)
end end
local snapshots = collect(remotes.replication.OnClientEvent) local snapshots = collect(remotes.replication.OnClientEvent)
return function(world: types.World) return function(world: types.World)
for snapshot in snapshots do for snapshot in snapshots do
for id, map in snapshot do for id, map in snapshot do
id = tonumber(id) id = tonumber(id)
if jecs.IS_PAIR(id) then if jecs.IS_PAIR(id) then
id = ecs_make_alive_id(world, id) id = ecs_make_alive_id(world, id)
end end
local set = map.set local set = map.set
if set then if set then
if jecs.is_tag(world, id) then if jecs.is_tag(world, id) then
for _, entity in set do for _, entity in set do
entity = ecs_map_get(world, entity) entity = ecs_map_get(world, entity)
world:add(entity, id) world:add(entity, id)
end end
else else
local values = map.values local values = map.values
for i, entity in set do for i, entity in set do
entity = ecs_map_get(world, entity) entity = ecs_map_get(world, entity)
world:set(entity, id, values[i]) world:set(entity, id, values[i])
end end
end end
end end
local removed = map.removed local removed = map.removed
if removed then if removed then
for i, e in removed do for i, e in removed do
if not world:contains(e) then if not world:contains(e) then
continue continue
end end
world:remove(e, id) world:remove(e, id)
end end
end end
end end
end end
end end

View file

@ -1,15 +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 = { export type Snapshot = {
[string]: { [string]: {
set: { jecs.Entity }?, set: { jecs.Entity }?,
values: { any }?, values: { any }?,
removed: { jecs.Entity }? removed: { jecs.Entity }?
} }
} }
return {} return {}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,190 +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 } local components = ct :: {[string]: jecs.Entity }
return function(world: ty.World) return function(world: ty.World)
--- integration test --- integration test
-- for _ = 1, 10 do -- for _ = 1, 10 do
-- local e = world:entity() -- local e = world:entity()
-- world:set(e, ct.TestA, true) -- world:set(e, ct.TestA, true)
-- end -- end
local storages = {} :: { [jecs.Entity]: {[jecs.Entity]: any }} local storages = {} :: { [jecs.Entity]: {[jecs.Entity]: any }}
local networked_components = {} local networked_components = {}
local networked_pairs = {} local networked_pairs = {}
for component in world:each(ct.Networked) do for component in world:each(ct.Networked) do
local name = world:get(component, jecs.Name) :: string local name = world:get(component, jecs.Name) :: string
if components[name] == nil then if components[name] == nil then
continue continue
end end
storages[component] = {} storages[component] = {}
table.insert(networked_components, component) table.insert(networked_components, component)
end end
for relation in world:each(ct.NetworkedPair) do for relation in world:each(ct.NetworkedPair) do
local name = world:get(relation, jecs.Name) :: string local name = world:get(relation, jecs.Name) :: string
if not components[name] then if not components[name] then
continue continue
end end
table.insert(networked_pairs, relation) table.insert(networked_pairs, relation)
end end
for _, component in networked_components do for _, component in networked_components do
local name = world:get(component, jecs.Name) :: string local name = world:get(component, jecs.Name) :: string
if not components[name] then if not components[name] then
error(`Networked Component (%id{component}%name{name})`) error(`Networked Component (%id{component}%name{name})`)
end end
local is_tag = jecs.is_tag(world, component) local is_tag = jecs.is_tag(world, component)
local storage = storages[component] local storage = storages[component]
if is_tag then if is_tag then
world:added(component, function(entity) world:added(component, function(entity)
storage[entity] = true storage[entity] = true
end) end)
else else
world:added(component, function(entity, _, value) world:added(component, function(entity, _, value)
storage[entity] = value storage[entity] = value
end) end)
world:changed(component, function(entity, _, value) world:changed(component, function(entity, _, value)
storage[entity] = value storage[entity] = value
end) end)
end end
world:removed(component, function(entity) world:removed(component, function(entity)
storage[entity] = "jecs.Remove" storage[entity] = "jecs.Remove"
end) end)
end end
for _, relation in networked_pairs do for _, relation in networked_pairs do
world:added(relation, function(entity, id, value) world:added(relation, function(entity, id, value)
local is_tag = jecs.is_tag(world, id) local is_tag = jecs.is_tag(world, id)
local storage = storages[id] local storage = storages[id]
if not storage then if not storage then
storage = {} storage = {}
storages[id] = storage storages[id] = storage
end end
if is_tag then if is_tag then
storage[entity] = true storage[entity] = true
else else
storage[entity] = value storage[entity] = value
end end
end) end)
world:changed(relation, function(entity, id, value) world:changed(relation, function(entity, id, value)
local is_tag = jecs.is_tag(world, id) local is_tag = jecs.is_tag(world, id)
if is_tag then if is_tag then
return return
end end
local storage = storages[id] local storage = storages[id]
if not storage then if not storage then
storage = {} storage = {}
storages[id] = storage storages[id] = storage
end end
storage[entity] = value storage[entity] = value
end) end)
world:removed(relation, function(entity, id) world:removed(relation, function(entity, id)
local storage = storages[id] local storage = storages[id]
if not storage then if not storage then
storage = {} storage = {}
storages[id] = storage storages[id] = storage
end end
storage[entity] = "jecs.Remove" storage[entity] = "jecs.Remove"
end) end)
end end
local players_added = collect(Players.PlayerAdded) local players_added = collect(Players.PlayerAdded)
return function() return function()
local snapshot_lazy: ty.Snapshot local snapshot_lazy: ty.Snapshot
local set_ids_lazy: { jecs.Entity } local set_ids_lazy: { jecs.Entity }
for player in players_added do for player in players_added do
if not snapshot_lazy then if not snapshot_lazy then
snapshot_lazy, set_ids_lazy = {}, {} snapshot_lazy, set_ids_lazy = {}, {}
for component, storage in storages do for component, storage in storages do
local set_values = {} local set_values = {}
local set_n = 0 local set_n = 0
local q = world:query(component) local q = world:query(component)
local is_tag = jecs.is_tag(world, component) local is_tag = jecs.is_tag(world, component)
for _, archetype in q:archetypes() do for _, archetype in q:archetypes() do
local entities = archetype.entities local entities = archetype.entities
local entities_len = #entities local entities_len = #entities
table.move(entities, 1, entities_len, set_n + 1, set_ids_lazy) table.move(entities, 1, entities_len, set_n + 1, set_ids_lazy)
if is_tag then if is_tag then
set_values = table.create(entities_len, true) set_values = table.create(entities_len, true)
else else
local column = archetype.columns[archetype.records[component]] local column = archetype.columns[archetype.records[component]]
table.move(column, 1, entities_len, set_n + 1, set_values) table.move(column, 1, entities_len, set_n + 1, set_values)
end end
set_n += entities_len set_n += entities_len
end end
local set = table.move(set_ids_lazy, 1, set_n, 1, {}) local set = table.move(set_ids_lazy, 1, set_n, 1, {})
snapshot_lazy[tostring(component)] = { snapshot_lazy[tostring(component)] = {
set = if set_n > 0 then set else nil, set = if set_n > 0 then set else nil,
values = if set_n > 0 then set_values else nil, values = if set_n > 0 then set_values else nil,
} }
end end
end end
remotes.replication:FireClient(player, snapshot_lazy) remotes.replication:FireClient(player, snapshot_lazy)
end end
local snapshot = {} :: ty.Snapshot local snapshot = {} :: ty.Snapshot
local set_ids = {} local set_ids = {}
local removed_ids = {} local removed_ids = {}
for component, storage in storages do for component, storage in storages do
local set_values = {} :: { any } local set_values = {} :: { any }
local set_n = 0 local set_n = 0
local removed_n = 0 local removed_n = 0
for e, v in storage do for e, v in storage do
if v ~= "jecs.Remove" then if v ~= "jecs.Remove" then
set_n += 1 set_n += 1
set_ids[set_n] = e set_ids[set_n] = e
set_values[set_n] = v or true set_values[set_n] = v or true
elseif not world:contains(e) then elseif not world:contains(e) then
removed_n += 1 removed_n += 1
removed_ids[removed_n] = e removed_ids[removed_n] = e
end end
end end
table.clear(storage) table.clear(storage)
local dirty = false local dirty = false
if set_n > 0 or removed_n > 0 then if set_n > 0 or removed_n > 0 then
dirty = true dirty = true
end end
if dirty then if dirty then
local removed = table.move(removed_ids, 1, removed_n, 1, {}) :: { jecs.Entity } local removed = table.move(removed_ids, 1, removed_n, 1, {}) :: { jecs.Entity }
local set = table.move(set_ids, 1, set_n, 1, {}) :: { jecs.Entity } local set = table.move(set_ids, 1, set_n, 1, {}) :: { jecs.Entity }
snapshot[tostring(component)] = { snapshot[tostring(component)] = {
set = if set_n > 0 then set else nil, set = if set_n > 0 then set else nil,
values = if set_n > 0 then set_values else nil, values = if set_n > 0 then set_values else nil,
removed = if removed_n > 0 then removed else nil removed = if removed_n > 0 then removed else nil
} }
end end
end end
if next(snapshot) ~= nil then if next(snapshot) ~= nil then
remotes.replication:FireAllClients(snapshot) remotes.replication:FireAllClients(snapshot)
end end
end end
end end

View file

@ -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)

View file

@ -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)

View file

@ -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()