mirror of
https://github.com/Ukendio/jecs.git
synced 2025-08-04 03:09:18 +00:00
Bump versions (#259)
This commit is contained in:
parent
998b1d3528
commit
b521fe750a
12 changed files with 396 additions and 586 deletions
211
addons/ob.luau
Executable file
211
addons/ob.luau
Executable file
|
@ -0,0 +1,211 @@
|
|||
--!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
|
||||
}
|
|
@ -1,338 +0,0 @@
|
|||
--!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
|
|
@ -1,190 +0,0 @@
|
|||
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,13 +2,12 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local schedule = require(ReplicatedStorage.schedule)
|
||||
local observers_add = require(ReplicatedStorage.observers_add)
|
||||
|
||||
local SYSTEM = schedule.SYSTEM
|
||||
local RUN = schedule.RUN
|
||||
|
||||
require(ReplicatedStorage.components)
|
||||
local world = observers_add(jecs.world())
|
||||
local world = jecs.world()
|
||||
|
||||
local systems = ServerScriptService.systems
|
||||
|
||||
|
|
|
@ -15,9 +15,6 @@ hero:
|
|||
- theme: alt
|
||||
text: API References
|
||||
link: /api/jecs.md
|
||||
- theme: alt
|
||||
text: Observers
|
||||
link: /api/observers.md
|
||||
|
||||
features:
|
||||
- title: Stupidly Fast
|
||||
|
|
|
@ -49,8 +49,8 @@ A tool for inspecting entity lifetimes
|
|||
|
||||
### Helpers
|
||||
|
||||
#### [jecs_observers](https://github.com/Ukendio/jecs/blob/main/addons/observers.luau)
|
||||
Observers for queries and signals for components
|
||||
#### [jecs_ob](https://github.com/Ukendio/jecs/blob/main/addons/ob.luau)
|
||||
Observers & Monitors for queries
|
||||
|
||||
### [hammer](https://github.com/Mark-Marks/hammer)
|
||||
A set of utilities for Jecs
|
||||
|
|
2
jecs.d.ts
vendored
2
jecs.d.ts
vendored
|
@ -49,7 +49,7 @@ export type Archetype<T extends unknown[]> = {
|
|||
type: string;
|
||||
entities: number[];
|
||||
columns: Column<unknown>[];
|
||||
columns_map: { [K in keyof T]: Column<T[K]> }
|
||||
columns_map: Record<Id, Column<T[number]>>
|
||||
};
|
||||
|
||||
type Iter<T extends unknown[]> = IterableFunction<LuaTuple<[Entity, ...T]>>;
|
||||
|
|
141
jecs.luau
141
jecs.luau
|
@ -53,6 +53,8 @@ export type Query<T...> = typeof(setmetatable(
|
|||
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
|
||||
archetypes: (self: Query<T...>) -> { Archetype },
|
||||
cached: (self: Query<T...>) -> Query<T...>,
|
||||
ids: { Id<any> },
|
||||
-- world: World
|
||||
},
|
||||
{} :: {
|
||||
__iter: Iter<T...>,
|
||||
|
@ -66,7 +68,6 @@ export type Observer = {
|
|||
query: QueryInner,
|
||||
}
|
||||
|
||||
|
||||
type archetype = {
|
||||
id: number,
|
||||
types: { i53 },
|
||||
|
@ -129,7 +130,11 @@ type world = {
|
|||
exists: (self: world, entity: i53) -> boolean,
|
||||
each: (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 = {
|
||||
|
@ -145,6 +150,10 @@ export type World = {
|
|||
|
||||
observable: Map<Id, Map<Id, { Observer }>>,
|
||||
|
||||
added: <T>(World, Id<T>, <e>(e: Entity<e>, id: Id<T>, value: T?) -> ()) -> () -> (),
|
||||
removed: <T>(World, Id<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
||||
changed: <T>(World, Id<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
||||
|
||||
--- Enforce a check on entities to be created within desired range
|
||||
range: (self: World, range_begin: number, range_end: number?) -> (),
|
||||
|
||||
|
@ -2213,6 +2222,14 @@ local function world_new()
|
|||
|
||||
local observable = {}
|
||||
|
||||
type Signal = { [i53]: { Listener<any> } }
|
||||
|
||||
local signals = {
|
||||
added = {} :: Signal,
|
||||
changed = {} :: Signal,
|
||||
removed = {} :: Signal
|
||||
}
|
||||
|
||||
local world = {
|
||||
archetype_edges = archetype_edges,
|
||||
|
||||
|
@ -2226,9 +2243,11 @@ local function world_new()
|
|||
max_component_id = ecs_max_component_id,
|
||||
|
||||
observable = observable,
|
||||
signals = signals,
|
||||
} :: world
|
||||
|
||||
|
||||
|
||||
local ROOT_ARCHETYPE = archetype_create(world, {}, "")
|
||||
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
||||
|
||||
|
@ -2578,6 +2597,108 @@ local function world_new()
|
|||
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,
|
||||
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): boolean
|
||||
|
||||
|
@ -3159,21 +3280,21 @@ return {
|
|||
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,
|
||||
|
||||
OnAdd = (EcsOnAdd :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||
OnRemove = (EcsOnRemove :: any) :: Entity<(entity: Entity, id: Id) -> ()>,
|
||||
OnChange = (EcsOnChange :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||
OnAdd = (EcsOnAdd :: any) :: Id<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||
OnRemove = (EcsOnRemove :: any) :: Id<(entity: Entity, id: Id) -> ()>,
|
||||
OnChange = (EcsOnChange :: any) :: Id<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||
ChildOf = (EcsChildOf :: any) :: Entity,
|
||||
Component = (EcsComponent :: any) :: Entity,
|
||||
Wildcard = (EcsWildcard :: any) :: Entity,
|
||||
w = (EcsWildcard :: any) :: Entity,
|
||||
Wildcard = (EcsWildcard :: any) :: Id,
|
||||
w = (EcsWildcard :: any) :: Id,
|
||||
OnDelete = (EcsOnDelete :: any) :: Entity,
|
||||
OnDeleteTarget = (EcsOnDeleteTarget :: any) :: Entity,
|
||||
Delete = (EcsDelete :: any) :: Entity,
|
||||
Remove = (EcsRemove :: any) :: Entity,
|
||||
Name = (EcsName :: any) :: Entity<string>,
|
||||
Name = (EcsName :: any) :: Id<string>,
|
||||
Exclusive = (EcsExclusive :: any) :: Entity,
|
||||
ArchetypeCreate = EcsOnArchetypeCreate,
|
||||
ArchetypeDelete = EcsOnArchetypeDelete,
|
||||
ArchetypeCreate = (EcsOnArchetypeCreate :: any) :: Entity,
|
||||
ArchetypeDelete = (EcsOnArchetypeDelete :: any) :: Entity,
|
||||
Rest = (EcsRest :: any) :: Entity,
|
||||
|
||||
pair = ECS_PAIR :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@rbxts/jecs",
|
||||
"version": "0.9.0-rc.5",
|
||||
"version": "0.9.0-rc.6",
|
||||
"description": "Stupidly fast Entity Component System",
|
||||
"main": "jecs.luau",
|
||||
"repository": {
|
||||
|
|
|
@ -2,42 +2,10 @@ local jecs = require("@jecs")
|
|||
local testkit = require("@testkit")
|
||||
local test = testkit.test()
|
||||
local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
||||
local observers_add = require("@addons/observers")
|
||||
local ob = require("@addons/ob")
|
||||
|
||||
TEST("addons/observers", function()
|
||||
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
|
||||
local world = jecs.world()
|
||||
|
||||
do CASE "Ensure ordering between signals and observers"
|
||||
local A = world:component()
|
||||
|
@ -48,11 +16,15 @@ TEST("addons/observers", function()
|
|||
count += 1
|
||||
end
|
||||
|
||||
world:observer(world:query(A, B), counter)
|
||||
ob.observer(world:query(A, B), counter)
|
||||
|
||||
world:added(A, counter)
|
||||
world:added(A, counter)
|
||||
|
||||
for _ in world:query(A) do
|
||||
|
||||
end
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
CHECK(count == 3)
|
||||
|
@ -69,7 +41,7 @@ TEST("addons/observers", function()
|
|||
count += 1
|
||||
end
|
||||
|
||||
world:observer(world:query(A), counter)
|
||||
ob.observer(world:query(A), counter)
|
||||
|
||||
local e = world:entity()
|
||||
world:set(e, A, false)
|
||||
|
@ -89,7 +61,7 @@ TEST("addons/observers", function()
|
|||
count += 1
|
||||
end
|
||||
|
||||
world:monitor(world:query(A), counter)
|
||||
ob.monitor(world:query(A), counter)
|
||||
|
||||
local e = world:entity()
|
||||
world:set(e, A, false)
|
||||
|
|
|
@ -1062,6 +1062,44 @@ TEST("world:each()", function()
|
|||
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()
|
||||
|
||||
do CASE "spawn entity under min range"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ukendio/jecs"
|
||||
version = "0.9.0-rc.5"
|
||||
version = "0.9.0-rc.6"
|
||||
registry = "https://github.com/UpliftGames/wally-index"
|
||||
realm = "shared"
|
||||
license = "MIT"
|
||||
|
|
Loading…
Reference in a new issue