mirror of
https://github.com/Ukendio/jecs.git
synced 2025-08-04 19:29:18 +00:00
Compare commits
No commits in common. "499afc20cd00e4b3eeee77fdbe501d1dc41e1173" and "998b1d35281f54aa562710cba61851a2194ae0c9" have entirely different histories.
499afc20cd
...
998b1d3528
14 changed files with 588 additions and 402 deletions
211
addons/ob.luau
211
addons/ob.luau
|
@ -1,211 +0,0 @@
|
||||||
--!strict
|
|
||||||
local jecs = require("@jecs")
|
|
||||||
|
|
||||||
type World = jecs.World
|
|
||||||
type Query<T...> = jecs.Query<T...>
|
|
||||||
|
|
||||||
type Id<T=any> = jecs.Id<T>
|
|
||||||
|
|
||||||
type Entity<T> = jecs.Entity<T>
|
|
||||||
|
|
||||||
export type Iter<T...> = (Observer<T...>) -> () -> (jecs.Entity, T...)
|
|
||||||
|
|
||||||
export type Observer<T...> = typeof(setmetatable(
|
|
||||||
{} :: {
|
|
||||||
iter: Iter<T...>,
|
|
||||||
entities: { Entity<nil> },
|
|
||||||
disconnect: (Observer<T...>) -> ()
|
|
||||||
},
|
|
||||||
{} :: {
|
|
||||||
__iter: Iter<T...>,
|
|
||||||
}
|
|
||||||
))
|
|
||||||
|
|
||||||
local function observers_new<T...>(
|
|
||||||
query: Query<T...>,
|
|
||||||
callback: ((Entity<nil>, Id<any>, value: any?) -> ())?
|
|
||||||
): Observer<T...>
|
|
||||||
|
|
||||||
query:cached()
|
|
||||||
|
|
||||||
local world = (query :: Query<T...> & { world: World }).world
|
|
||||||
callback = callback
|
|
||||||
|
|
||||||
local archetypes = {}
|
|
||||||
local terms = query.ids
|
|
||||||
local first = terms[1]
|
|
||||||
|
|
||||||
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
|
||||||
local observer_on_create = observers_on_create[#observers_on_create]
|
|
||||||
observer_on_create.callback = function(archetype)
|
|
||||||
archetypes[archetype.id] = true
|
|
||||||
end
|
|
||||||
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
|
|
||||||
local observer_on_delete = observers_on_delete[#observers_on_delete]
|
|
||||||
observer_on_delete.callback = function(archetype)
|
|
||||||
archetypes[archetype.id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local entity_index = world.entity_index :: any
|
|
||||||
local i = 0
|
|
||||||
local entities = {}
|
|
||||||
|
|
||||||
local function emplaced<T, a>(
|
|
||||||
entity: jecs.Entity<T>,
|
|
||||||
id: jecs.Id<a>,
|
|
||||||
value: a?
|
|
||||||
)
|
|
||||||
local r = entity_index.sparse_array[jecs.ECS_ID(entity)]
|
|
||||||
|
|
||||||
local archetype = r.archetype
|
|
||||||
|
|
||||||
if archetypes[archetype.id] then
|
|
||||||
i += 1
|
|
||||||
entities[i] = entity
|
|
||||||
if callback ~= nil then
|
|
||||||
callback(entity, id, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, term in terms do
|
|
||||||
world:added(term, emplaced)
|
|
||||||
world:changed(term, emplaced)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function disconnect()
|
|
||||||
table.remove(observers_on_create, table.find(
|
|
||||||
observers_on_create,
|
|
||||||
observer_on_create
|
|
||||||
))
|
|
||||||
|
|
||||||
table.remove(observers_on_delete, table.find(
|
|
||||||
observers_on_delete,
|
|
||||||
observer_on_delete
|
|
||||||
))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function iter()
|
|
||||||
local row = i
|
|
||||||
return function()
|
|
||||||
if row == 0 then
|
|
||||||
i = 0
|
|
||||||
table.clear(entities)
|
|
||||||
end
|
|
||||||
local entity = entities[row]
|
|
||||||
row -= 1
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local observer = {
|
|
||||||
disconnect = disconnect,
|
|
||||||
entities = entities,
|
|
||||||
__iter = iter,
|
|
||||||
iter = iter
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(observer, observer)
|
|
||||||
|
|
||||||
return (observer :: any) :: Observer<T...>
|
|
||||||
end
|
|
||||||
|
|
||||||
local function monitors_new<T...>(
|
|
||||||
query: Query<T...>,
|
|
||||||
callback: ((Entity<nil>, Id<any>, value: any?) -> ())?
|
|
||||||
): Observer<T...>
|
|
||||||
|
|
||||||
query:cached()
|
|
||||||
|
|
||||||
local world = (query :: Query<T...> & { world: World }).world
|
|
||||||
|
|
||||||
local archetypes = {}
|
|
||||||
local terms = query.ids
|
|
||||||
local first = terms[1]
|
|
||||||
|
|
||||||
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
|
||||||
local observer_on_create = observers_on_create[#observers_on_create]
|
|
||||||
observer_on_create.callback = function(archetype)
|
|
||||||
archetypes[archetype.id] = true
|
|
||||||
end
|
|
||||||
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
|
|
||||||
local observer_on_delete = observers_on_delete[#observers_on_delete]
|
|
||||||
observer_on_delete.callback = function(archetype)
|
|
||||||
archetypes[archetype.id] = nil
|
|
||||||
end
|
|
||||||
local entity_index = world.entity_index :: any
|
|
||||||
local i = 0
|
|
||||||
local entities = {}
|
|
||||||
|
|
||||||
local function emplaced<T, a>(
|
|
||||||
entity: jecs.Entity<T>,
|
|
||||||
id: jecs.Id<a>,
|
|
||||||
value: a?
|
|
||||||
)
|
|
||||||
local r = jecs.entity_index_try_get_fast(
|
|
||||||
entity_index, entity :: any) :: jecs.Record
|
|
||||||
|
|
||||||
local archetype = r.archetype
|
|
||||||
|
|
||||||
if archetypes[archetype.id] then
|
|
||||||
i += 1
|
|
||||||
entities[i] = entity
|
|
||||||
if callback ~= nil then
|
|
||||||
callback(entity, id, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function removed(entity: jecs.Entity, component: jecs.Id)
|
|
||||||
local EcsOnRemove = jecs.OnRemove :: jecs.Id
|
|
||||||
if callback ~= nil then
|
|
||||||
callback(entity, EcsOnRemove)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, term in terms do
|
|
||||||
world:added(term, emplaced)
|
|
||||||
world:removed(term, removed)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function disconnect()
|
|
||||||
table.remove(observers_on_create, table.find(
|
|
||||||
observers_on_create,
|
|
||||||
observer_on_create
|
|
||||||
))
|
|
||||||
|
|
||||||
table.remove(observers_on_delete, table.find(
|
|
||||||
observers_on_delete,
|
|
||||||
observer_on_delete
|
|
||||||
))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function iter()
|
|
||||||
local row = i
|
|
||||||
return function()
|
|
||||||
if row == 0 then
|
|
||||||
i = 0
|
|
||||||
table.clear(entities)
|
|
||||||
end
|
|
||||||
local entity = entities[row]
|
|
||||||
row -= 1
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local observer = {
|
|
||||||
disconnect = disconnect,
|
|
||||||
entities = entities,
|
|
||||||
__iter = iter,
|
|
||||||
iter = iter
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(observer, observer)
|
|
||||||
|
|
||||||
return (observer :: any) :: Observer<T...>
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
monitor = monitors_new,
|
|
||||||
observer = observers_new
|
|
||||||
}
|
|
338
addons/observers.luau
Executable file
338
addons/observers.luau
Executable file
|
@ -0,0 +1,338 @@
|
||||||
|
--!strict
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
|
||||||
|
|
||||||
|
export type Iter<T...> = (query: Query<T...>) -> () -> (jecs.Entity, T...)
|
||||||
|
|
||||||
|
type Id<T=any> = jecs.Id<T>
|
||||||
|
|
||||||
|
type Query<T...> = typeof(setmetatable(
|
||||||
|
{} :: {
|
||||||
|
iter: Iter<T...>,
|
||||||
|
with: (<a>(Query<T...>, Id<a>) -> Query<T...>)
|
||||||
|
& (<a, b>(Query<T...>, Id<a>, Id<b>) -> Query<T...>)
|
||||||
|
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
|
||||||
|
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
|
||||||
|
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
|
||||||
|
without: (<a>(Query<T...>, Id<a>) -> Query<T...>)
|
||||||
|
& (<a, b>(Query<T...>, Id<a>, Id<b>) -> Query<T...>)
|
||||||
|
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
|
||||||
|
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
|
||||||
|
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
|
||||||
|
archetypes: (self: Query<T...>) -> { jecs.Archetype },
|
||||||
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
|
},
|
||||||
|
{} :: {
|
||||||
|
__iter: Iter<T...>,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
export type PatchedWorld = jecs.World & {
|
||||||
|
added: <T>(PatchedWorld, jecs.Id<T>, <e>(e: jecs.Entity<e>, id: jecs.Id<T>, value: T?) -> ()) -> () -> (),
|
||||||
|
removed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (),
|
||||||
|
changed: <T>(PatchedWorld, jecs.Id<T>, <e>(e: jecs.Entity<e>, id: jecs.Id<T>, value: T) -> ()) -> () -> (),
|
||||||
|
observer: <T...>(PatchedWorld, Query<T...>, callback: ((jecs.Entity, jecs.Id, any?) -> ())?) -> () -> () -> (jecs.Entity),
|
||||||
|
monitor: <T...>(PatchedWorld, Query<T...>, callback: ((jecs.Entity, jecs.Id, any?) -> ())?) -> () -> () -> (jecs.Entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
local function observers_new<T...>(
|
||||||
|
world: PatchedWorld,
|
||||||
|
query: Query<T...>,
|
||||||
|
callback: (<T, a>(jecs.Entity<T>, jecs.Id<a>, value: a?) -> ())?
|
||||||
|
)
|
||||||
|
query = query:cached()
|
||||||
|
callback = callback
|
||||||
|
|
||||||
|
local archetypes = {}
|
||||||
|
local terms = query.ids
|
||||||
|
local first = terms[1]
|
||||||
|
|
||||||
|
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
||||||
|
observers_on_create[#observers_on_create].callback = function(archetype)
|
||||||
|
archetypes[archetype.id] = true
|
||||||
|
end
|
||||||
|
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
|
||||||
|
observers_on_delete[#observers_on_delete].callback = function(archetype)
|
||||||
|
archetypes[archetype.id] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local entity_index = world.entity_index :: any
|
||||||
|
local i = 0
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local function emplaced<T, a>(
|
||||||
|
entity: jecs.Entity<T>,
|
||||||
|
id: jecs.Id<a>,
|
||||||
|
value: a?
|
||||||
|
)
|
||||||
|
local r = entity_index.sparse_array[jecs.ECS_ID(entity)]
|
||||||
|
|
||||||
|
local archetype = r.archetype
|
||||||
|
|
||||||
|
if archetypes[archetype.id] then
|
||||||
|
i += 1
|
||||||
|
entities[i] = entity
|
||||||
|
if callback ~= nil then
|
||||||
|
callback(entity, id, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, term in terms do
|
||||||
|
world:added(term, emplaced)
|
||||||
|
world:changed(term, emplaced)
|
||||||
|
end
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local row = i
|
||||||
|
return function()
|
||||||
|
if row == 0 then
|
||||||
|
i = 0
|
||||||
|
table.clear(entities)
|
||||||
|
end
|
||||||
|
local entity = entities[row]
|
||||||
|
row -= 1
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function join(world, component)
|
||||||
|
local sparse_array = {}
|
||||||
|
local dense_array = {}
|
||||||
|
local values = {}
|
||||||
|
local max_id = 0
|
||||||
|
|
||||||
|
world:added(component, function(entity, id, value)
|
||||||
|
max_id += 1
|
||||||
|
sparse_array[entity] = max_id
|
||||||
|
dense_array[max_id] = entity
|
||||||
|
values[max_id] = value
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:removed(component, function(entity, id)
|
||||||
|
local e_swap = dense_array[max_id]
|
||||||
|
local v_swap = values[max_id]
|
||||||
|
|
||||||
|
local dense = sparse_array[entity]
|
||||||
|
dense_array[dense] = e_swap
|
||||||
|
values[dense] = v_swap
|
||||||
|
|
||||||
|
sparse_array[entity] = nil
|
||||||
|
dense_array[max_id] = nil
|
||||||
|
values[max_id] = nil
|
||||||
|
max_id -= 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:changed(component, function(entity, id, value)
|
||||||
|
values[sparse_array[entity]] = value
|
||||||
|
end)
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local i = max_id
|
||||||
|
return function(): ...any
|
||||||
|
i -= 1
|
||||||
|
if i == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local e = dense_array[i]
|
||||||
|
return e, values[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function monitors_new(world, query, callback)
|
||||||
|
query = query:cached()
|
||||||
|
|
||||||
|
local archetypes = {}
|
||||||
|
local terms = query.ids
|
||||||
|
local first = terms[1]
|
||||||
|
|
||||||
|
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
||||||
|
observers_on_create[#observers_on_create].callback = function(archetype)
|
||||||
|
archetypes[archetype.id] = true
|
||||||
|
end
|
||||||
|
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
|
||||||
|
observers_on_delete[#observers_on_delete].callback = function(archetype)
|
||||||
|
archetypes[archetype.id] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local entity_index = world.entity_index :: any
|
||||||
|
local i = 0
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local function emplaced<T, a>(
|
||||||
|
entity: jecs.Entity<T>,
|
||||||
|
id: jecs.Id<a>,
|
||||||
|
value: a?
|
||||||
|
)
|
||||||
|
local r = jecs.entity_index_try_get_fast(
|
||||||
|
entity_index, entity :: any) :: jecs.Record
|
||||||
|
|
||||||
|
local archetype = r.archetype
|
||||||
|
|
||||||
|
if archetypes[archetype.id] then
|
||||||
|
i += 1
|
||||||
|
entities[i] = entity
|
||||||
|
if callback ~= nil then
|
||||||
|
callback(entity, id, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function removed(entity: jecs.Entity, component: jecs.Id)
|
||||||
|
local EcsOnRemove = jecs.OnRemove :: jecs.Id
|
||||||
|
if callback ~= nil then
|
||||||
|
callback(entity, EcsOnRemove)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, term in terms do
|
||||||
|
world:added(term, emplaced)
|
||||||
|
world:removed(term, removed)
|
||||||
|
end
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local row = i
|
||||||
|
return function()
|
||||||
|
if row == 0 then
|
||||||
|
i = 0
|
||||||
|
table.clear(entities)
|
||||||
|
end
|
||||||
|
local entity = entities[row]
|
||||||
|
row -= 1
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function observers_add(world: jecs.World): PatchedWorld
|
||||||
|
type Signal = { [jecs.Entity]: { (...any) -> () } }
|
||||||
|
|
||||||
|
local world_mut = world :: jecs.World & {[string]: any}
|
||||||
|
|
||||||
|
local signals = {
|
||||||
|
added = {} :: Signal,
|
||||||
|
emplaced = {} :: Signal,
|
||||||
|
removed = {} :: Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
world_mut.added = function<T>(
|
||||||
|
_: jecs.World,
|
||||||
|
component: jecs.Id<T>,
|
||||||
|
fn: (e: jecs.Entity, id: jecs.Id, value: T) -> ()
|
||||||
|
)
|
||||||
|
local listeners = signals.added[component]
|
||||||
|
if not listeners then
|
||||||
|
listeners = {}
|
||||||
|
signals.added[component] = listeners
|
||||||
|
|
||||||
|
local function on_add(entity, id, value)
|
||||||
|
for _, listener in listeners :: any do
|
||||||
|
listener(entity, id, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local existing_hook = world:get(component, jecs.OnAdd)
|
||||||
|
if existing_hook then
|
||||||
|
table.insert(listeners, existing_hook)
|
||||||
|
end
|
||||||
|
|
||||||
|
local idr = world.component_index[component]
|
||||||
|
if idr then
|
||||||
|
idr.on_add = on_add
|
||||||
|
else
|
||||||
|
world:set(component, jecs.OnAdd, on_add)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(listeners, fn)
|
||||||
|
return function()
|
||||||
|
local n = #listeners
|
||||||
|
local i = table.find(listeners, fn)
|
||||||
|
listeners[i] = listeners[n]
|
||||||
|
listeners[n] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
world_mut.changed = function<T>(
|
||||||
|
_: jecs.World,
|
||||||
|
component: jecs.Id<T>,
|
||||||
|
fn: (e: jecs.Entity, id: jecs.Id, value: T) -> ()
|
||||||
|
)
|
||||||
|
local listeners = signals.emplaced[component]
|
||||||
|
if not listeners then
|
||||||
|
listeners = {}
|
||||||
|
signals.emplaced[component] = listeners
|
||||||
|
local function on_change(entity, id, value: any)
|
||||||
|
for _, listener in listeners :: any do
|
||||||
|
listener(entity, id, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local existing_hook = world:get(component, jecs.OnChange)
|
||||||
|
if existing_hook then
|
||||||
|
table.insert(listeners, existing_hook)
|
||||||
|
end
|
||||||
|
local idr = world.component_index[component]
|
||||||
|
if idr then
|
||||||
|
idr.on_change = on_change
|
||||||
|
else
|
||||||
|
world:set(component, jecs.OnChange, on_change)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(listeners, fn)
|
||||||
|
return function()
|
||||||
|
local n = #listeners
|
||||||
|
local i = table.find(listeners, fn)
|
||||||
|
listeners[i] = listeners[n]
|
||||||
|
listeners[n] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
world_mut.removed = function<T>(
|
||||||
|
_: jecs.World,
|
||||||
|
component: jecs.Id<T>,
|
||||||
|
fn: (e: jecs.Entity, id: jecs.Id) -> ()
|
||||||
|
)
|
||||||
|
local listeners = signals.removed[component]
|
||||||
|
if not listeners then
|
||||||
|
listeners = {}
|
||||||
|
signals.removed[component] = listeners
|
||||||
|
local function on_remove(entity, id)
|
||||||
|
for _, listener in listeners :: any do
|
||||||
|
listener(entity, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local existing_hook = world:get(component, jecs.OnRemove)
|
||||||
|
if existing_hook then
|
||||||
|
table.insert(listeners, existing_hook)
|
||||||
|
end
|
||||||
|
|
||||||
|
local idr = world.component_index[component]
|
||||||
|
if idr then
|
||||||
|
idr.on_remove = on_remove
|
||||||
|
else
|
||||||
|
world:set(component, jecs.OnRemove, on_remove)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(listeners, fn)
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local n = #listeners
|
||||||
|
local i = table.find(listeners, fn)
|
||||||
|
listeners[i] = listeners[n]
|
||||||
|
listeners[n] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
world_mut.signals = signals
|
||||||
|
|
||||||
|
world_mut.observer = observers_new
|
||||||
|
|
||||||
|
world_mut.monitor = monitors_new
|
||||||
|
|
||||||
|
world_mut.trackers = {}
|
||||||
|
|
||||||
|
return world_mut :: PatchedWorld
|
||||||
|
end
|
||||||
|
|
||||||
|
return observers_add
|
190
demo/src/ReplicatedStorage/observers_add.luau
Executable file
190
demo/src/ReplicatedStorage/observers_add.luau
Executable file
|
@ -0,0 +1,190 @@
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local jecs = require(ReplicatedStorage.ecs)
|
||||||
|
|
||||||
|
type Observer<T...> = {
|
||||||
|
callback: (jecs.Entity) -> (),
|
||||||
|
query: jecs.Query<T...>,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PatchedWorld = jecs.World & {
|
||||||
|
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) -> ()) -> (),
|
||||||
|
changed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id<T>, value: T) -> ()) -> (),
|
||||||
|
-- deleted: (PatchedWorld, () -> ()) -> () -> (),
|
||||||
|
observer: (PatchedWorld, Observer<any>) -> (),
|
||||||
|
monitor: (PatchedWorld, Observer<any>) -> (),
|
||||||
|
}
|
||||||
|
|
||||||
|
local function observers_new(world, description)
|
||||||
|
local query = description.query
|
||||||
|
local callback = description.callback
|
||||||
|
local terms = query.filter_with :: { jecs.Id }
|
||||||
|
if not terms then
|
||||||
|
local ids = query.ids
|
||||||
|
query.filter_with = ids
|
||||||
|
terms = ids
|
||||||
|
end
|
||||||
|
|
||||||
|
local entity_index = world.entity_index :: any
|
||||||
|
local function emplaced(entity: jecs.Entity)
|
||||||
|
local r = jecs.entity_index_try_get_fast(
|
||||||
|
entity_index, entity :: any)
|
||||||
|
|
||||||
|
if not r then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local archetype = r.archetype
|
||||||
|
|
||||||
|
if jecs.query_match(query, archetype) then
|
||||||
|
callback(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, term in terms do
|
||||||
|
world:added(term, emplaced)
|
||||||
|
world:changed(term, emplaced)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function monitors_new(world, description)
|
||||||
|
local query = description.query
|
||||||
|
local callback = description.callback
|
||||||
|
local terms = query.filter_with :: { jecs.Id }
|
||||||
|
if not terms then
|
||||||
|
local ids = query.ids
|
||||||
|
query.filter_with = ids
|
||||||
|
terms = ids
|
||||||
|
end
|
||||||
|
|
||||||
|
local entity_index = world.entity_index :: any
|
||||||
|
local function emplaced(entity: jecs.Entity)
|
||||||
|
local r = jecs.entity_index_try_get_fast(
|
||||||
|
entity_index, entity :: any)
|
||||||
|
|
||||||
|
if not r then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local archetype = r.archetype
|
||||||
|
|
||||||
|
if jecs.query_match(query, archetype) then
|
||||||
|
callback(entity, jecs.OnAdd)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function removed(entity: jecs.Entity, component: jecs.Id)
|
||||||
|
local r = jecs.entity_index_try_get_fast(
|
||||||
|
entity_index, entity :: any)
|
||||||
|
|
||||||
|
if not r then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local archetype = r.archetype
|
||||||
|
|
||||||
|
if jecs.query_match(query, archetype) then
|
||||||
|
callback(entity, jecs.OnRemove)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, term in terms do
|
||||||
|
world:added(term, emplaced)
|
||||||
|
world:removed(term, removed)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function observers_add(world: jecs.World): PatchedWorld
|
||||||
|
local signals = {
|
||||||
|
added = {},
|
||||||
|
emplaced = {},
|
||||||
|
removed = {},
|
||||||
|
deleted = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
world = world :: jecs.World & {[string]: any}
|
||||||
|
|
||||||
|
world.added = function(_, component, fn)
|
||||||
|
local listeners = signals.added[component]
|
||||||
|
if not listeners then
|
||||||
|
listeners = {}
|
||||||
|
signals.added[component] = listeners
|
||||||
|
|
||||||
|
local idr = jecs.id_record_ensure(world :: any, component :: any)
|
||||||
|
local rw = jecs.pair(component, jecs.Wildcard)
|
||||||
|
local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
|
||||||
|
local function on_add(entity: number, id: number, value: any)
|
||||||
|
for _, listener in listeners do
|
||||||
|
listener(entity, id, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
world:set(component, jecs.OnAdd, on_add)
|
||||||
|
idr.hooks.on_add = on_add :: any
|
||||||
|
idr_r.hooks.on_add = on_add :: any
|
||||||
|
end
|
||||||
|
table.insert(listeners, fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
world.changed = function(_, component, fn)
|
||||||
|
local listeners = signals.emplaced[component]
|
||||||
|
if not listeners then
|
||||||
|
listeners = {}
|
||||||
|
signals.emplaced[component] = listeners
|
||||||
|
local idr = jecs.id_record_ensure(world :: any, component :: any)
|
||||||
|
local rw = jecs.pair(component, jecs.Wildcard)
|
||||||
|
local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
|
||||||
|
local function on_change(entity: number, id: number, value: any)
|
||||||
|
for _, listener in listeners do
|
||||||
|
listener(entity, id, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
world:set(component, jecs.OnChange, on_change)
|
||||||
|
idr.hooks.on_change = on_change :: any
|
||||||
|
idr_r.hooks.on_change = on_change :: any
|
||||||
|
end
|
||||||
|
table.insert(listeners, fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
world.removed = function(_, component, fn)
|
||||||
|
local listeners = signals.removed[component]
|
||||||
|
if not listeners then
|
||||||
|
listeners = {}
|
||||||
|
signals.removed[component] = listeners
|
||||||
|
local idr = jecs.id_record_ensure(world :: any, component :: any)
|
||||||
|
local rw = jecs.pair(component, jecs.Wildcard)
|
||||||
|
local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
|
||||||
|
local function on_remove(entity: number, id: number, value: any)
|
||||||
|
for _, listener in listeners do
|
||||||
|
listener(entity, id, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
world:set(component, jecs.OnRemove, on_remove)
|
||||||
|
idr.hooks.on_remove = on_remove :: any
|
||||||
|
idr_r.hooks.on_remove = on_remove :: any
|
||||||
|
end
|
||||||
|
table.insert(listeners, fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
world.signals = signals
|
||||||
|
|
||||||
|
world.observer = observers_new
|
||||||
|
|
||||||
|
world.monitor = monitors_new
|
||||||
|
|
||||||
|
-- local world_delete = world.delete
|
||||||
|
|
||||||
|
-- world.deleted = function(_, fn)
|
||||||
|
-- local listeners = signals.deleted
|
||||||
|
-- table.insert(listeners, fn)
|
||||||
|
-- end
|
||||||
|
-- world.delete = function(world, entity)
|
||||||
|
-- world_delete(world, entity)
|
||||||
|
-- for _, fn in signals.deleted do
|
||||||
|
-- fn(entity)
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
return world :: PatchedWorld
|
||||||
|
end
|
||||||
|
|
||||||
|
return observers_add
|
|
@ -2,12 +2,13 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local ServerScriptService = game:GetService("ServerScriptService")
|
local ServerScriptService = game:GetService("ServerScriptService")
|
||||||
local jecs = require(ReplicatedStorage.ecs)
|
local jecs = require(ReplicatedStorage.ecs)
|
||||||
local schedule = require(ReplicatedStorage.schedule)
|
local schedule = require(ReplicatedStorage.schedule)
|
||||||
|
local observers_add = require(ReplicatedStorage.observers_add)
|
||||||
|
|
||||||
local SYSTEM = schedule.SYSTEM
|
local SYSTEM = schedule.SYSTEM
|
||||||
local RUN = schedule.RUN
|
local RUN = schedule.RUN
|
||||||
|
|
||||||
require(ReplicatedStorage.components)
|
require(ReplicatedStorage.components)
|
||||||
local world = jecs.world()
|
local world = observers_add(jecs.world())
|
||||||
|
|
||||||
local systems = ServerScriptService.systems
|
local systems = ServerScriptService.systems
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ Builtin component type. This ID is for naming components, but realistically you
|
||||||
jecs.Rest: Id
|
jecs.Rest: Id
|
||||||
```
|
```
|
||||||
|
|
||||||
Builtin component type. This ID is simply for denoting the end of the range for builtin component IDs.
|
Builtin component type. This ID is for setting up a callback that is invoked when an instance of a component is changed.
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ Creates a new component. Do note components are entities as well, meaning jecs a
|
||||||
These are meant to be added onto other entities through `add` and `set`
|
These are meant to be added onto other entities through `add` and `set`
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function World:component<T>(): Entity<T> -- The new component.
|
function World:component<T>(): Entity<T> -- The new componen.
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
|
@ -15,6 +15,9 @@ hero:
|
||||||
- theme: alt
|
- theme: alt
|
||||||
text: API References
|
text: API References
|
||||||
link: /api/jecs.md
|
link: /api/jecs.md
|
||||||
|
- theme: alt
|
||||||
|
text: Observers
|
||||||
|
link: /api/observers.md
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- title: Stupidly Fast
|
- title: Stupidly Fast
|
||||||
|
|
|
@ -49,8 +49,8 @@ A tool for inspecting entity lifetimes
|
||||||
|
|
||||||
### Helpers
|
### Helpers
|
||||||
|
|
||||||
#### [jecs_ob](https://github.com/Ukendio/jecs/blob/main/addons/ob.luau)
|
#### [jecs_observers](https://github.com/Ukendio/jecs/blob/main/addons/observers.luau)
|
||||||
Observers & Monitors for queries
|
Observers for queries and signals for components
|
||||||
|
|
||||||
### [hammer](https://github.com/Mark-Marks/hammer)
|
### [hammer](https://github.com/Mark-Marks/hammer)
|
||||||
A set of utilities for Jecs
|
A set of utilities for Jecs
|
||||||
|
|
8
jecs.d.ts
vendored
8
jecs.d.ts
vendored
|
@ -49,7 +49,7 @@ export type Archetype<T extends unknown[]> = {
|
||||||
type: string;
|
type: string;
|
||||||
entities: number[];
|
entities: number[];
|
||||||
columns: Column<unknown>[];
|
columns: Column<unknown>[];
|
||||||
columns_map: Record<Id, Column<T[number]>>
|
columns_map: { [K in keyof T]: Column<T[K]> }
|
||||||
};
|
};
|
||||||
|
|
||||||
type Iter<T extends unknown[]> = IterableFunction<LuaTuple<[Entity, ...T]>>;
|
type Iter<T extends unknown[]> = IterableFunction<LuaTuple<[Entity, ...T]>>;
|
||||||
|
@ -247,10 +247,6 @@ export class World {
|
||||||
* @returns A Query object to iterate over results.
|
* @returns A Query object to iterate over results.
|
||||||
*/
|
*/
|
||||||
query<T extends Id[]>(...components: T): Query<InferComponents<T>>;
|
query<T extends Id[]>(...components: T): Query<InferComponents<T>>;
|
||||||
|
|
||||||
added<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>, value: T) => void): () => void
|
|
||||||
changed<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>, value: T) => void): () => void
|
|
||||||
removed<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>) => void): () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function world(): World;
|
export function world(): World;
|
||||||
|
@ -327,4 +323,4 @@ export type ComponentRecord = {
|
||||||
export function component_record(world: World, id: Id): ComponentRecord
|
export function component_record(world: World, id: Id): ComponentRecord
|
||||||
|
|
||||||
export function bulk_insert<const C extends Id[]>(world: World, entity: Entity, ids: C, values: InferComponents<C>): void
|
export function bulk_insert<const C extends Id[]>(world: World, entity: Entity, ids: C, values: InferComponents<C>): void
|
||||||
export function bulk_remove(world: World, entity: Entity, ids: Id[]): void
|
export function bulk_remove(world: World, entity: Entity, ids: Id[]): void
|
141
jecs.luau
141
jecs.luau
|
@ -53,8 +53,6 @@ export type Query<T...> = typeof(setmetatable(
|
||||||
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
|
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
|
||||||
archetypes: (self: Query<T...>) -> { Archetype },
|
archetypes: (self: Query<T...>) -> { Archetype },
|
||||||
cached: (self: Query<T...>) -> Query<T...>,
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
ids: { Id<any> },
|
|
||||||
-- world: World
|
|
||||||
},
|
},
|
||||||
{} :: {
|
{} :: {
|
||||||
__iter: Iter<T...>,
|
__iter: Iter<T...>,
|
||||||
|
@ -68,6 +66,7 @@ export type Observer = {
|
||||||
query: QueryInner,
|
query: QueryInner,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type archetype = {
|
type archetype = {
|
||||||
id: number,
|
id: number,
|
||||||
types: { i53 },
|
types: { i53 },
|
||||||
|
@ -130,11 +129,7 @@ type world = {
|
||||||
exists: (self: world, entity: i53) -> boolean,
|
exists: (self: world, entity: i53) -> boolean,
|
||||||
each: (self: world, id: i53) -> () -> i53,
|
each: (self: world, id: i53) -> () -> i53,
|
||||||
children: (self: world, id: i53) -> () -> i53,
|
children: (self: world, id: i53) -> () -> i53,
|
||||||
query: (world, ...i53) -> Query<...any>,
|
query: (world, ...i53) -> Query<...any>
|
||||||
|
|
||||||
added: (world, i53, (e: i53, id: i53, value: any?) -> ()) -> () -> (),
|
|
||||||
changed: (world, i53, (e: i53, id: i53, value: any?) -> ()) -> () -> (),
|
|
||||||
removed: (world, i53, (e: i53, id: i53) -> ()) -> () -> (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type World = {
|
export type World = {
|
||||||
|
@ -150,10 +145,6 @@ export type World = {
|
||||||
|
|
||||||
observable: Map<Id, Map<Id, { Observer }>>,
|
observable: Map<Id, Map<Id, { Observer }>>,
|
||||||
|
|
||||||
added: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T?) -> ()) -> () -> (),
|
|
||||||
removed: <T>(World, Entity<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
|
||||||
changed: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
|
||||||
|
|
||||||
--- Enforce a check on entities to be created within desired range
|
--- Enforce a check on entities to be created within desired range
|
||||||
range: (self: World, range_begin: number, range_end: number?) -> (),
|
range: (self: World, range_begin: number, range_end: number?) -> (),
|
||||||
|
|
||||||
|
@ -2222,14 +2213,6 @@ local function world_new()
|
||||||
|
|
||||||
local observable = {}
|
local observable = {}
|
||||||
|
|
||||||
type Signal = { [i53]: { Listener<any> } }
|
|
||||||
|
|
||||||
local signals = {
|
|
||||||
added = {} :: Signal,
|
|
||||||
changed = {} :: Signal,
|
|
||||||
removed = {} :: Signal
|
|
||||||
}
|
|
||||||
|
|
||||||
local world = {
|
local world = {
|
||||||
archetype_edges = archetype_edges,
|
archetype_edges = archetype_edges,
|
||||||
|
|
||||||
|
@ -2243,11 +2226,9 @@ local function world_new()
|
||||||
max_component_id = ecs_max_component_id,
|
max_component_id = ecs_max_component_id,
|
||||||
|
|
||||||
observable = observable,
|
observable = observable,
|
||||||
signals = signals,
|
|
||||||
} :: world
|
} :: world
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local ROOT_ARCHETYPE = archetype_create(world, {}, "")
|
local ROOT_ARCHETYPE = archetype_create(world, {}, "")
|
||||||
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
||||||
|
|
||||||
|
@ -2597,108 +2578,6 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
type Listener<T> = (e: i53, id: i53, value: T?) -> ()
|
|
||||||
|
|
||||||
world.added = function<T>(_: world, component: i53, fn: Listener<T>)
|
|
||||||
local listeners = signals.added[component]
|
|
||||||
if not listeners then
|
|
||||||
listeners = {}
|
|
||||||
signals.added[component] = listeners
|
|
||||||
|
|
||||||
local function on_add(entity, id, value)
|
|
||||||
for _, listener in listeners :: { Listener<T> } do
|
|
||||||
listener(entity, id, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local existing_hook = inner_world_get(world, component, EcsOnAdd) :: Listener<T>
|
|
||||||
if existing_hook then
|
|
||||||
table.insert(listeners, existing_hook)
|
|
||||||
end
|
|
||||||
|
|
||||||
local idr = world.component_index[component]
|
|
||||||
if idr then
|
|
||||||
idr.on_add = on_add
|
|
||||||
else
|
|
||||||
inner_world_set(world, component, EcsOnAdd, on_add)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(listeners, fn)
|
|
||||||
return function()
|
|
||||||
local n = #listeners
|
|
||||||
local i = table.find(listeners, fn)
|
|
||||||
listeners[i] = listeners[n]
|
|
||||||
listeners[n] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
world.changed = function<T>(
|
|
||||||
_: world,
|
|
||||||
component: i53,
|
|
||||||
fn: Listener<T>
|
|
||||||
)
|
|
||||||
local listeners = signals.changed[component]
|
|
||||||
if not listeners then
|
|
||||||
listeners = {}
|
|
||||||
signals.changed[component] = listeners
|
|
||||||
local function on_change(entity, id, value: any)
|
|
||||||
for _, listener in listeners :: { Listener<T> } do
|
|
||||||
listener(entity, id, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local existing_hook = inner_world_get(world, component, EcsOnChange) :: Listener<T>
|
|
||||||
if existing_hook then
|
|
||||||
table.insert(listeners, existing_hook)
|
|
||||||
end
|
|
||||||
|
|
||||||
local idr = world.component_index[component]
|
|
||||||
if idr then
|
|
||||||
idr.on_change = on_change
|
|
||||||
else
|
|
||||||
inner_world_set(world, component, EcsOnChange, on_change)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(listeners, fn)
|
|
||||||
return function()
|
|
||||||
local n = #listeners
|
|
||||||
local i = table.find(listeners, fn)
|
|
||||||
listeners[i] = listeners[n]
|
|
||||||
listeners[n] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
world.removed = function<T>(_: world, component: i53, fn: (i53, i53) -> ())
|
|
||||||
local listeners = signals.removed[component]
|
|
||||||
if not listeners then
|
|
||||||
listeners = {}
|
|
||||||
signals.removed[component] = listeners
|
|
||||||
local function on_remove(entity, id)
|
|
||||||
for _, listener in listeners :: { Listener<T> } do
|
|
||||||
listener(entity, id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local existing_hook = inner_world_get(world, component, EcsOnRemove) :: Listener<T>
|
|
||||||
if existing_hook then
|
|
||||||
table.insert(listeners, existing_hook)
|
|
||||||
end
|
|
||||||
|
|
||||||
local idr = world.component_index[component]
|
|
||||||
if idr then
|
|
||||||
idr.on_remove = on_remove
|
|
||||||
else
|
|
||||||
inner_world_set(world, component, EcsOnRemove, on_remove)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(listeners, fn)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
local n = #listeners
|
|
||||||
local i = table.find(listeners, fn)
|
|
||||||
listeners[i] = listeners[n]
|
|
||||||
listeners[n] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function inner_world_has(world: World, entity: i53,
|
local function inner_world_has(world: World, entity: i53,
|
||||||
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): boolean
|
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): boolean
|
||||||
|
|
||||||
|
@ -3280,21 +3159,21 @@ return {
|
||||||
meta = (ECS_META :: any) :: <T, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
|
meta = (ECS_META :: any) :: <T, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
|
||||||
is_tag = (ecs_is_tag :: any) :: <T>(World, Id<T>) -> boolean,
|
is_tag = (ecs_is_tag :: any) :: <T>(World, Id<T>) -> boolean,
|
||||||
|
|
||||||
OnAdd = (EcsOnAdd :: any) :: Id<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
OnAdd = (EcsOnAdd :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||||
OnRemove = (EcsOnRemove :: any) :: Id<(entity: Entity, id: Id) -> ()>,
|
OnRemove = (EcsOnRemove :: any) :: Entity<(entity: Entity, id: Id) -> ()>,
|
||||||
OnChange = (EcsOnChange :: any) :: Id<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
OnChange = (EcsOnChange :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||||
ChildOf = (EcsChildOf :: any) :: Entity,
|
ChildOf = (EcsChildOf :: any) :: Entity,
|
||||||
Component = (EcsComponent :: any) :: Entity,
|
Component = (EcsComponent :: any) :: Entity,
|
||||||
Wildcard = (EcsWildcard :: any) :: Id,
|
Wildcard = (EcsWildcard :: any) :: Entity,
|
||||||
w = (EcsWildcard :: any) :: Id,
|
w = (EcsWildcard :: any) :: Entity,
|
||||||
OnDelete = (EcsOnDelete :: any) :: Entity,
|
OnDelete = (EcsOnDelete :: any) :: Entity,
|
||||||
OnDeleteTarget = (EcsOnDeleteTarget :: any) :: Entity,
|
OnDeleteTarget = (EcsOnDeleteTarget :: any) :: Entity,
|
||||||
Delete = (EcsDelete :: any) :: Entity,
|
Delete = (EcsDelete :: any) :: Entity,
|
||||||
Remove = (EcsRemove :: any) :: Entity,
|
Remove = (EcsRemove :: any) :: Entity,
|
||||||
Name = (EcsName :: any) :: Id<string>,
|
Name = (EcsName :: any) :: Entity<string>,
|
||||||
Exclusive = (EcsExclusive :: any) :: Entity,
|
Exclusive = (EcsExclusive :: any) :: Entity,
|
||||||
ArchetypeCreate = (EcsOnArchetypeCreate :: any) :: Entity,
|
ArchetypeCreate = EcsOnArchetypeCreate,
|
||||||
ArchetypeDelete = (EcsOnArchetypeDelete :: any) :: Entity,
|
ArchetypeDelete = EcsOnArchetypeDelete,
|
||||||
Rest = (EcsRest :: any) :: Entity,
|
Rest = (EcsRest :: any) :: Entity,
|
||||||
|
|
||||||
pair = ECS_PAIR :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
|
pair = ECS_PAIR :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.9.0-rc.6",
|
"version": "0.9.0-rc.5",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "jecs.luau",
|
"main": "jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -2,10 +2,42 @@ local jecs = require("@jecs")
|
||||||
local testkit = require("@testkit")
|
local testkit = require("@testkit")
|
||||||
local test = testkit.test()
|
local test = testkit.test()
|
||||||
local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
||||||
local ob = require("@addons/ob")
|
local observers_add = require("@addons/observers")
|
||||||
|
|
||||||
TEST("addons/observers", function()
|
TEST("addons/observers", function()
|
||||||
local world = jecs.world()
|
local world = observers_add(jecs.world())
|
||||||
|
|
||||||
|
do CASE "Should work even if set after the component has been used"
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
world:set(world:entity(), A, 2)
|
||||||
|
local ran = false
|
||||||
|
world:added(A, function()
|
||||||
|
ran = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
local entity = world:entity()
|
||||||
|
world:set(entity, A, 3)
|
||||||
|
|
||||||
|
CHECK(ran)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should not override hook"
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
local count = 1
|
||||||
|
local function counter()
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
world:set(A, jecs.OnAdd, counter)
|
||||||
|
world:added(A, counter)
|
||||||
|
world:set(world:entity(), A, false)
|
||||||
|
CHECK(count == (1 + 2))
|
||||||
|
world:set(world:entity(), A, false)
|
||||||
|
|
||||||
|
CHECK(count == (1 + (2 * 2)))
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "Ensure ordering between signals and observers"
|
do CASE "Ensure ordering between signals and observers"
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
@ -16,15 +48,11 @@ TEST("addons/observers", function()
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
ob.observer(world:query(A, B), counter)
|
world:observer(world:query(A, B), counter)
|
||||||
|
|
||||||
world:added(A, counter)
|
world:added(A, counter)
|
||||||
world:added(A, counter)
|
world:added(A, counter)
|
||||||
|
|
||||||
for _ in world:query(A) do
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:add(e, A)
|
world:add(e, A)
|
||||||
CHECK(count == 3)
|
CHECK(count == 3)
|
||||||
|
@ -41,7 +69,7 @@ TEST("addons/observers", function()
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
ob.observer(world:query(A), counter)
|
world:observer(world:query(A), counter)
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:set(e, A, false)
|
world:set(e, A, false)
|
||||||
|
@ -61,7 +89,7 @@ TEST("addons/observers", function()
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
ob.monitor(world:query(A), counter)
|
world:monitor(world:query(A), counter)
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:set(e, A, false)
|
world:set(e, A, false)
|
||||||
|
|
|
@ -1062,44 +1062,6 @@ TEST("world:each()", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:added", function()
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
do CASE "Should work even if set after the component has been used"
|
|
||||||
local A = world:component()
|
|
||||||
|
|
||||||
world:set(world:entity(), A, 2)
|
|
||||||
local ran = false
|
|
||||||
world:added(A, function()
|
|
||||||
ran = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
local entity = world:entity()
|
|
||||||
world:set(entity, A, 3)
|
|
||||||
|
|
||||||
CHECK(ran)
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "Should not override hook"
|
|
||||||
local A = world:component()
|
|
||||||
|
|
||||||
local count = 1
|
|
||||||
local function counter()
|
|
||||||
count += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
world:set(A, jecs.OnAdd, counter)
|
|
||||||
world:added(A, counter)
|
|
||||||
world:set(world:entity(), A, false)
|
|
||||||
CHECK(count == (1 + 2))
|
|
||||||
world:set(world:entity(), A, false)
|
|
||||||
|
|
||||||
CHECK(count == (1 + (2 * 2)))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
end)
|
|
||||||
|
|
||||||
TEST("world:range()", function()
|
TEST("world:range()", function()
|
||||||
|
|
||||||
do CASE "spawn entity under min range"
|
do CASE "spawn entity under min range"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.9.0-rc.6"
|
version = "0.9.0-rc.5"
|
||||||
registry = "https://github.com/UpliftGames/wally-index"
|
registry = "https://github.com/UpliftGames/wally-index"
|
||||||
realm = "shared"
|
realm = "shared"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
Loading…
Reference in a new issue