mirror of
https://github.com/Ukendio/jecs.git
synced 2025-08-04 11:19:17 +00:00
Compare commits
76 commits
Author | SHA1 | Date | |
---|---|---|---|
|
0b6bfea5c8 | ||
|
3cfce10a4a | ||
|
add9ad3939 | ||
|
4153a7cdfe | ||
|
4230a0a797 | ||
|
499afc20cd | ||
|
f284de6ec1 | ||
|
abc0b1ec22 | ||
|
b521fe750a | ||
|
998b1d3528 | ||
|
0606bf70f0 | ||
|
f2a803c0d8 | ||
|
3e46b723e9 | ||
|
3ab1d970e2 | ||
|
3e2d40e706 | ||
|
1c2dee57d3 | ||
|
3777585677 | ||
|
f792c98585 | ||
|
7b43748f18 | ||
|
666a3ef6de | ||
|
69911093a3 | ||
|
59df0bf2a3 | ||
|
3e995c9d7d | ||
|
78fe5338cf | ||
|
ca0689c92b | ||
|
117a5e0ca7 | ||
|
d99088ea1e | ||
|
012b5e2bfa | ||
|
54b21001ab | ||
|
9c09686a69 | ||
|
ebc39c8b28 | ||
|
7b86084b94 | ||
|
25ceda5cee | ||
|
fc56b6f716 | ||
|
c30328527a | ||
|
c3853023d0 | ||
|
7b253e1c2a | ||
|
210d62d463 | ||
|
3f6f8c1739 | ||
|
7f66d21e6d | ||
|
5334d8734d | ||
|
a6ae67250c | ||
|
9ae32bcce8 | ||
|
f6731069aa | ||
|
c67dfcbd24 | ||
|
e1545710db | ||
|
aa178981dc | ||
|
ad5ed3b5ea | ||
|
362490d25e | ||
|
6c1793f853 | ||
|
6b6f6fb961 | ||
|
29350e6ec3 | ||
|
eed1b6179e | ||
|
cf94a48a40 | ||
|
23540e5919 | ||
|
169ec09ed5 | ||
|
a9891abf6d | ||
|
6dfb428296 | ||
|
b92cf9ab76 | ||
|
5aedb5e730 | ||
|
1c524f1587 | ||
|
6053038cc1 | ||
|
29305cac5d | ||
|
8fd32978b4 | ||
|
c9d888aeb9 | ||
|
155d51a080 | ||
|
b425150b0c | ||
|
13facc3719 | ||
|
a6ba9f4bd5 | ||
|
53f705ac2e | ||
|
ff4b0bf612 | ||
|
9b57189c3a | ||
|
4ff492ceaf | ||
|
7c8358656a | ||
|
d6e720f200 | ||
|
3c7f3b4eb3 |
35 changed files with 4273 additions and 2494 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -2,6 +2,20 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `jecs.Exclusive` trait for making exclusive relationships.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `jecs.ChildOf` to be an exclusive relationship, which means you can only have one `ChildOf` pair on an entity.
|
||||||
|
|
||||||
|
## 0.7.2
|
||||||
|
### Added
|
||||||
|
- `jecs.entity_index_try_get_fast` back as to not break the observer addon.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- A linting problem with the types for `quer:with` and `query:without`.
|
||||||
|
|
||||||
|
|
||||||
## 0.7.0
|
## 0.7.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -10,7 +24,7 @@
|
||||||
- `bulk_insert` and `bulk_remove` respectively for moving an entity to an archetype without intermediate steps.
|
- `bulk_insert` and `bulk_remove` respectively for moving an entity to an archetype without intermediate steps.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- The fields `archetype.records[id]` and `archetype.counts[id` have been removed from the archetype struct and been moved to the component record `component_index[id].records[archetype.id]` and `component_index[id].counts[archetype.id]` respectively.
|
- The fields `archetype.records[id]` and `archetype.counts[id]` have been removed from the archetype struct and been moved to the component record `component_index[id].records[archetype.id]` and `component_index[id].counts[archetype.id]` respectively.
|
||||||
- Removed the metatable `jecs.World`. Use `jecs.world()` to create your World.
|
- Removed the metatable `jecs.World`. Use `jecs.world()` to create your World.
|
||||||
- Archetypes will no longer be garbage collected when invalidated, allowing them to be recycled to save a lot of performance during frequent deletion.
|
- Archetypes will no longer be garbage collected when invalidated, allowing them to be recycled to save a lot of performance during frequent deletion.
|
||||||
- Removed `jecs.entity_index_try_get_fast`. Use `jecs.entity_index_try_get` instead.
|
- Removed `jecs.entity_index_try_get_fast`. Use `jecs.entity_index_try_get` instead.
|
||||||
|
|
217
addons/ob.luau
Executable file
217
addons/ob.luau
Executable file
|
@ -0,0 +1,217 @@
|
||||||
|
--!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
|
||||||
|
if jecs.IS_PAIR(term) then
|
||||||
|
term = jecs.ECS_PAIR_FIRST(term)
|
||||||
|
end
|
||||||
|
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, jecs.OnAdd)
|
||||||
|
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
|
||||||
|
if jecs.IS_PAIR(term) then
|
||||||
|
term = jecs.ECS_PAIR_FIRST(term)
|
||||||
|
end
|
||||||
|
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,268 +0,0 @@
|
||||||
local jecs = require("@jecs")
|
|
||||||
|
|
||||||
export type PatchedWorld = jecs.World & {
|
|
||||||
added: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (),
|
|
||||||
removed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (),
|
|
||||||
changed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (),
|
|
||||||
observer: (
|
|
||||||
PatchedWorld,
|
|
||||||
any,
|
|
||||||
(jecs.Entity) -> ()
|
|
||||||
) -> (),
|
|
||||||
monitor: (
|
|
||||||
PatchedWorld,
|
|
||||||
any,
|
|
||||||
(jecs.Entity, jecs.Id) -> ()
|
|
||||||
) -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
local function observers_new(world, query, 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, id, value)
|
|
||||||
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 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)
|
|
||||||
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
|
|
||||||
local EcsOnRemove = jecs.OnRemove :: jecs.Id
|
|
||||||
callback(entity, EcsOnRemove)
|
|
||||||
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
|
|
||||||
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.hooks.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.hooks.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.hooks.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,61 +1,65 @@
|
||||||
|
|
||||||
--!optimize 2
|
--!optimize 2
|
||||||
--!native
|
--!native
|
||||||
|
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
local jecs = require(ReplicatedStorage.Lib:Clone())
|
||||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
|
||||||
local jecs = require(ReplicatedStorage.Lib)
|
|
||||||
local pair = jecs.pair
|
local pair = jecs.pair
|
||||||
local ecs = jecs.world()
|
local ecs = jecs.world()
|
||||||
local mirror = require(ReplicatedStorage.mirror)
|
local mirror = require(ReplicatedStorage.mirror:Clone())
|
||||||
local mcs = mirror.World.new()
|
local mcs = mirror.world()
|
||||||
|
|
||||||
local C1 = ecs:component()
|
local C1 = ecs:component()
|
||||||
local C2 = ecs:entity()
|
local C2 = ecs:entity()
|
||||||
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
|
||||||
local C3 = ecs:entity()
|
local C3 = ecs:entity()
|
||||||
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
|
||||||
local C4 = ecs:entity()
|
local C4 = ecs:entity()
|
||||||
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))
|
|
||||||
local E3 = mcs:entity()
|
local E3 = mcs:entity()
|
||||||
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
|
||||||
local E4 = mcs:entity()
|
local E4 = mcs:entity()
|
||||||
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
|
|
||||||
|
local m = mcs:entity()
|
||||||
|
local j = ecs:entity()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ParameterGenerator = function()
|
ParameterGenerator = function()
|
||||||
local j = ecs:entity()
|
|
||||||
ecs:set(j, C1, true)
|
|
||||||
local m = mcs:entity()
|
|
||||||
mcs:set(m, E1, true)
|
|
||||||
for i = 1, 1000 do
|
|
||||||
local friend1 = ecs:entity()
|
|
||||||
local friend2 = mcs:entity()
|
|
||||||
|
|
||||||
ecs:add(friend1, pair(C2, j))
|
|
||||||
ecs:add(friend1, pair(C3, j))
|
|
||||||
ecs:add(friend1, pair(C4, j))
|
|
||||||
|
|
||||||
mcs:add(friend2, pair(E2, m))
|
|
||||||
mcs:add(friend2, pair(E3, m))
|
|
||||||
mcs:add(friend2, pair(E4, m))
|
|
||||||
end
|
|
||||||
return {
|
|
||||||
m = m,
|
|
||||||
j = j,
|
|
||||||
}
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Functions = {
|
Functions = {
|
||||||
Mirror = function(_, a)
|
Mirror = function()
|
||||||
mcs:delete(a.m)
|
for i = 1, 10 do
|
||||||
|
local friend2 = mcs:entity()
|
||||||
|
mcs:add(friend2, pair(E2, m))
|
||||||
|
mcs:add(friend2, pair(E3, m))
|
||||||
|
mcs:add(friend2, pair(E4, m))
|
||||||
|
|
||||||
|
-- local r = mirror.entity_index_try_get_fast(mcs.entity_index, friend2)
|
||||||
|
-- local archetype = r.archetype
|
||||||
|
|
||||||
|
-- mirror.archetype_destroy(mcs, archetype)
|
||||||
|
|
||||||
|
mcs:delete(m)
|
||||||
|
m = mcs:entity(m)
|
||||||
|
end
|
||||||
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Jecs = function(_, a)
|
Jecs = function()
|
||||||
ecs:delete(a.j)
|
for i = 1, 10 do
|
||||||
|
local friend1 = ecs:entity()
|
||||||
|
ecs:add(friend1, pair(C2, j))
|
||||||
|
ecs:add(friend1, pair(C3, j))
|
||||||
|
ecs:add(friend1, pair(C4, j))
|
||||||
|
|
||||||
|
-- local r = jecs.entity_index_try_get_fast(ecs.entity_index, friend1)
|
||||||
|
-- local archetype = r.archetype
|
||||||
|
|
||||||
|
-- jecs.archetype_destroy(ecs, archetype)
|
||||||
|
|
||||||
|
ecs:delete(j)
|
||||||
|
j = ecs:entity()
|
||||||
|
end
|
||||||
|
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local jecs = require(ReplicatedStorage.Lib:Clone())
|
local jecs = require(ReplicatedStorage.Lib:Clone())
|
||||||
local ecs = jecs.world()
|
local ecs = jecs.world()
|
||||||
local mirror = require(ReplicatedStorage.mirror:Clone())
|
local mirror = require(ReplicatedStorage.mirror:Clone())
|
||||||
local mcs = mirror.World.new()
|
local mcs = mirror.world()
|
||||||
|
|
||||||
|
|
||||||
local C1 = ecs:component()
|
local C1 = ecs:component()
|
||||||
local C2 = ecs:component()
|
local C2 = ecs:component()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
--!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 newWorld = Matter.World.new()
|
local newWorld = Matter.World.new()
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,17 @@
|
||||||
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:Clone())
|
||||||
local pair = jecs.pair
|
local pair = jecs.pair
|
||||||
local ecs = jecs.world()
|
local ecs = jecs.world()
|
||||||
local mirror = require(ReplicatedStorage.mirror)
|
local mirror = require(ReplicatedStorage.mirror:Clone())
|
||||||
local mcs = mirror.World.new()
|
local mcs = mirror.world()
|
||||||
|
|
||||||
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))
|
||||||
|
ecs:add(C2, jecs.Exclusive)
|
||||||
|
|
||||||
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()
|
||||||
|
@ -33,16 +35,17 @@ return {
|
||||||
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, pair(E2, E3))
|
||||||
mcs:remove(m, E3)
|
mcs:remove(m, pair(E2, E3))
|
||||||
|
mcs:add(m, pair(E2, E4))
|
||||||
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, pair(C2, C3))
|
||||||
ecs:remove(j, C3)
|
ecs:add(j, pair(C2, C4))
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
local function collect<T...>(
|
|
||||||
signal: {
|
--!strict
|
||||||
Connect: (RBXScriptSignal<T...>, fn: (T...) -> ()) -> RBXScriptConnection
|
local function collect(signal)
|
||||||
}
|
|
||||||
): () -> (T...)
|
|
||||||
local enqueued = {}
|
local enqueued = {}
|
||||||
|
|
||||||
local i = 0
|
local i = 0
|
||||||
|
|
||||||
local connection = (signal :: any):Connect(function(...)
|
local connection = signal:Connect(function(...)
|
||||||
table.insert(enqueued, { ... })
|
table.insert(enqueued, { ... })
|
||||||
i += 1
|
i += 1
|
||||||
end)
|
end)
|
||||||
|
@ -25,4 +23,11 @@ local function collect<T...>(
|
||||||
end, connection
|
end, connection
|
||||||
end
|
end
|
||||||
|
|
||||||
return collect
|
type Signal<T... = ...any> = {
|
||||||
|
Connect: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection,
|
||||||
|
ConnectParallel: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection,
|
||||||
|
Once: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection,
|
||||||
|
Wait: (self: Signal<T...>) -> (T...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect :: <T...>(Signal<T...>) -> (() -> (T...), RBXScriptConnection)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
--!strict
|
||||||
|
local RunService = game:GetService("RunService")
|
||||||
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")
|
||||||
|
@ -5,31 +7,58 @@ 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 InstanceMapping = jecs.component() :: jecs.Id<Instance>
|
||||||
jecs.meta(Renderable, Networked)
|
jecs.meta(InstanceMapping, jecs.OnAdd, function(component)
|
||||||
|
jecs.meta(component, jecs.OnAdd, function(entity, _, instance)
|
||||||
|
if RunService:IsServer() then
|
||||||
|
instance:SetAttribute("entity_server")
|
||||||
|
else
|
||||||
|
instance:SetAttribute("entity_client")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function networked_id(ct)
|
||||||
|
jecs.meta(ct, Networked)
|
||||||
|
return ct
|
||||||
|
end
|
||||||
|
local function networked_pair(ct)
|
||||||
|
jecs.meta(ct, NetworkedPair)
|
||||||
|
return ct
|
||||||
|
end
|
||||||
|
local function instance_mapping_id(ct)
|
||||||
|
jecs.meta(ct, InstanceMapping)
|
||||||
|
return ct
|
||||||
|
end
|
||||||
|
|
||||||
local Poison = jecs.component() :: jecs.Id<number>
|
local Renderable = jecs.component() :: types.Id<Instance>
|
||||||
jecs.meta(Poison, Networked)
|
local Poison = jecs.component() :: types.Id<number>
|
||||||
|
local Health = jecs.component() :: types.Id<number>
|
||||||
local Health = jecs.component() :: jecs.Id<number>
|
local Player = jecs.component() :: types.Id<Player>
|
||||||
jecs.meta(Health, Networked)
|
local Debuff = jecs.tag() :: types.Entity
|
||||||
|
local Lifetime = jecs.component() :: types.Id<{
|
||||||
local Player = jecs.component() :: jecs.Id<Player>
|
duration: number,
|
||||||
jecs.meta(Player, Networked)
|
created: number
|
||||||
|
}>
|
||||||
|
local Destroy = jecs.tag()
|
||||||
|
|
||||||
local components = {
|
local components = {
|
||||||
Renderable = Renderable,
|
Renderable = networked_id(instance_mapping_id(Renderable)),
|
||||||
Player = Player,
|
Player = networked_id(Player),
|
||||||
Poison = Poison,
|
Poison = networked_id(Poison),
|
||||||
Health = Health,
|
Health = networked_id(Health),
|
||||||
|
Lifetime = networked_id(Lifetime),
|
||||||
|
Debuff = networked_id(Debuff),
|
||||||
|
Destroy = networked_id(Destroy),
|
||||||
|
|
||||||
|
-- We have to define that some builtin IDs can also be networked
|
||||||
|
ChildOf = networked_pair(jecs.ChildOf),
|
||||||
|
|
||||||
Networked = Networked,
|
Networked = Networked,
|
||||||
NetworkedPair = NetworkedPair,
|
NetworkedPair = NetworkedPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, component in components do
|
for name, component in components :: {[string]: types.Id<any> } do
|
||||||
jecs.meta(component, jecs.Name, name)
|
jecs.meta(component, jecs.Name, name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
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 = observers_add(jecs.world())
|
local world = jecs.world()
|
||||||
|
|
||||||
local systems = ReplicatedStorage.systems
|
local systems = ReplicatedStorage.systems
|
||||||
SYSTEM(world, systems.receive_replication)
|
SYSTEM(world, systems.receive_replication)
|
||||||
|
SYSTEM(world, systems.entities_delete)
|
||||||
RUN(world)
|
RUN(world)
|
||||||
|
|
|
@ -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
|
|
|
@ -1,40 +1,32 @@
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local types = require("../ReplicatedStorage/types")
|
local types = require("../ReplicatedStorage/types")
|
||||||
|
|
||||||
type Signal<T...> = {
|
|
||||||
Connect: (Signal<T...>, fn: (T...) -> ()) -> RBXScriptConnection
|
|
||||||
}
|
|
||||||
type Remote<T...> = {
|
type Remote<T...> = {
|
||||||
FireClient: (Remote<T...>, T...) -> (),
|
FireClient: (Remote<T...>, Player, T...) -> (),
|
||||||
FireAllClients: (Remote<T...>, T...) -> (),
|
FireAllClients: (Remote<T...>, T...) -> (),
|
||||||
FireServer: (Remote<T...>) -> (),
|
FireServer: (Remote<T...>, T...) -> (),
|
||||||
OnServerEvent: {
|
OnServerEvent: RBXScriptSignal<(Player, T...)>,
|
||||||
Connect: (any, fn: (Player, T...) -> () ) -> ()
|
OnClientEvent: RBXScriptSignal<T...>
|
||||||
},
|
|
||||||
OnClientEvent: {
|
|
||||||
Connect: (any, fn: (T...) -> () ) -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local function stream_ensure(name): Remote<any>
|
local function stream_ensure(name)
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
local function datagram_ensure(name): Remote<any>
|
local function datagram_ensure(name)
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
13
demo/src/ReplicatedStorage/systems/entities_delete.luau
Executable file
13
demo/src/ReplicatedStorage/systems/entities_delete.luau
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
--!strict
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local types = require(ReplicatedStorage.types)
|
||||||
|
local ct = require(ReplicatedStorage.components)
|
||||||
|
|
||||||
|
local function entities_delete(world: types.World, dt: number)
|
||||||
|
for e in world:each(ct.Destroy) do
|
||||||
|
world:delete(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return entities_delete
|
|
@ -2,45 +2,51 @@ 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 components = require("../components")
|
||||||
|
|
||||||
|
|
||||||
local function ecs_map_get(world, id)
|
local client_ids: {[jecs.Entity]: jecs.Entity } = {}
|
||||||
local deserialised_id = client_ids[id]
|
|
||||||
|
|
||||||
if not deserialised_id then
|
local function ecs_ensure_entity(world: jecs.World, id: jecs.Entity)
|
||||||
if world:has(id, jecs.Name) then
|
local e = 0
|
||||||
deserialised_id = world:entity(id)
|
|
||||||
else
|
local ser_id = id
|
||||||
deserialised_id = world:entity()
|
local deser_id = client_ids[ser_id]
|
||||||
|
if deser_id then
|
||||||
|
if deser_id == 0 then
|
||||||
|
local new_id = world:entity()
|
||||||
|
client_ids[ser_id] = new_id
|
||||||
|
deser_id = new_id
|
||||||
end
|
end
|
||||||
|
else
|
||||||
client_ids[id] = deserialised_id
|
if not world:exists(ser_id)
|
||||||
|
or (world:contains(ser_id) and not world:get(ser_id, jecs.Name))
|
||||||
|
then
|
||||||
|
deser_id = world:entity()
|
||||||
|
else
|
||||||
|
if world:contains(ser_id) and world:get(ser_id, jecs.Name) then
|
||||||
|
deser_id = ser_id
|
||||||
|
else
|
||||||
|
deser_id = world:entity()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
client_ids[ser_id] = deser_id
|
||||||
end
|
end
|
||||||
|
|
||||||
-- local deserialised_id = client_ids[id]
|
e = deser_id
|
||||||
-- if not deserialised_id then
|
|
||||||
-- if world:has(id, jecs.Name) then
|
|
||||||
-- deserialised_id = world:entity(id)
|
|
||||||
-- else
|
|
||||||
-- if world:exists(id) then
|
|
||||||
-- deserialised_id = world:entity()
|
|
||||||
-- else
|
|
||||||
-- deserialised_id = world:entity(id)
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
-- client_ids[id] = deserialised_id
|
|
||||||
-- end
|
|
||||||
|
|
||||||
return deserialised_id
|
return e
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ecs_make_alive_id(world, id)
|
-- local rel_render = `e{jecs.ECS_ID(rel)}v{jecs.ECS_GENERATION(rel)}`
|
||||||
local rel = jecs.ECS_PAIR_FIRST(id)
|
-- local tgt_render = `e{jecs.ECS_ID(tgt)}v{jecs.ECS_GENERATION(tgt)}`
|
||||||
local tgt = jecs.ECS_PAIR_SECOND(id)
|
local function ecs_deser_pairs(world, token)
|
||||||
|
local tokens = string.split(token, ",")
|
||||||
|
local rel = tonumber(tokens[1])
|
||||||
|
local tgt = tonumber(tokens[2])
|
||||||
|
|
||||||
rel = ecs_map_get(world, rel)
|
rel = ecs_ensure_entity(world, rel)
|
||||||
tgt = ecs_map_get(world, tgt)
|
tgt = ecs_ensure_entity(world, tgt)
|
||||||
|
|
||||||
return jecs.pair(rel, tgt)
|
return jecs.pair(rel, tgt)
|
||||||
end
|
end
|
||||||
|
@ -48,25 +54,31 @@ end
|
||||||
local snapshots = collect(remotes.replication.OnClientEvent)
|
local snapshots = collect(remotes.replication.OnClientEvent)
|
||||||
|
|
||||||
return function(world: types.World)
|
return function(world: types.World)
|
||||||
|
for entity in world:each(components.Destroy) do
|
||||||
|
client_ids[entity] = nil
|
||||||
|
end
|
||||||
for snapshot in snapshots do
|
for snapshot in snapshots do
|
||||||
for id, map in snapshot do
|
for ser_id, map in snapshot do
|
||||||
id = tonumber(id)
|
local id = tonumber(ser_id)
|
||||||
if jecs.IS_PAIR(id) then
|
if not id then
|
||||||
id = ecs_make_alive_id(world, id)
|
id = ecs_deser_pairs(world, ser_id)
|
||||||
|
else
|
||||||
|
id = ecs_ensure_entity(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_ensure_entity(world, entity)
|
||||||
world:add(entity, id)
|
world:add(entity, id)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
local t = os.clock()
|
||||||
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_ensure_entity(world, entity)
|
||||||
world:set(entity, id, values[i])
|
world:set(entity, id, values[i])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -74,11 +86,9 @@ return function(world: types.World)
|
||||||
local removed = map.removed
|
local removed = map.removed
|
||||||
|
|
||||||
if removed then
|
if removed then
|
||||||
for i, e in removed do
|
for _, entity in removed do
|
||||||
if not world:contains(e) then
|
entity = ecs_ensure_entity(world, entity)
|
||||||
continue
|
world:remove(entity, id)
|
||||||
end
|
|
||||||
world:remove(e, id)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||||
local observers_add = require("../ReplicatedStorage/observers_add")
|
|
||||||
|
|
||||||
export type World = typeof(observers_add(jecs.world()))
|
export type World = typeof(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 = {
|
||||||
|
|
|
@ -2,18 +2,20 @@ 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 = observers_add(jecs.world())
|
local world = jecs.world()
|
||||||
|
|
||||||
local systems = ServerScriptService.systems
|
local systems = ServerScriptService.systems
|
||||||
|
|
||||||
SYSTEM(world, systems.replication)
|
SYSTEM(world, systems.replication)
|
||||||
SYSTEM(world, systems.players_added)
|
SYSTEM(world, systems.players_added)
|
||||||
SYSTEM(world, systems.poison_hurts)
|
SYSTEM(world, systems.poison_hurts)
|
||||||
|
SYSTEM(world, systems.health_regen)
|
||||||
|
SYSTEM(world, systems.lifetimes_expire)
|
||||||
SYSTEM(world, systems.life_is_painful)
|
SYSTEM(world, systems.life_is_painful)
|
||||||
|
SYSTEM(world, ReplicatedStorage.systems.entities_delete)
|
||||||
RUN(world, 0)
|
RUN(world, 0)
|
||||||
|
|
15
demo/src/ServerScriptService/systems/health_regen.luau
Executable file
15
demo/src/ServerScriptService/systems/health_regen.luau
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local ct = require(ReplicatedStorage.components)
|
||||||
|
local types = require(ReplicatedStorage.types)
|
||||||
|
|
||||||
|
return function(world: types.World, dt: number)
|
||||||
|
for e in world:query(ct.Player):without(ct.Health) do
|
||||||
|
world:set(e, ct.Health, 100)
|
||||||
|
end
|
||||||
|
|
||||||
|
for e, health in world:query(ct.Health) do
|
||||||
|
if math.random() < 1 / 60 / 30 then
|
||||||
|
world:set(e, ct.Health, 100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,12 +1,19 @@
|
||||||
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)
|
||||||
|
local jecs = require(ReplicatedStorage.ecs)
|
||||||
|
|
||||||
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
|
if math.random() < (1 / 60 / 7) then
|
||||||
world:set(e, ct.Health, 100)
|
for e in world:each(ct.Health) do
|
||||||
end
|
local poison = world:entity()
|
||||||
for e in world:query(ct.Player, ct.Health):without(ct.Poison) do
|
world:add(poison, ct.Debuff)
|
||||||
world:set(e, ct.Poison, 10)
|
world:add(poison, jecs.pair(jecs.ChildOf, e))
|
||||||
|
world:set(poison, ct.Poison, 10)
|
||||||
|
world:set(poison, ct.Lifetime, {
|
||||||
|
duration = 3,
|
||||||
|
created = os.clock()
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
12
demo/src/ServerScriptService/systems/lifetimes_expire.luau
Executable file
12
demo/src/ServerScriptService/systems/lifetimes_expire.luau
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local ct = require(ReplicatedStorage.components)
|
||||||
|
local types = require(ReplicatedStorage.types)
|
||||||
|
|
||||||
|
return function(world: types.World, dt: number)
|
||||||
|
for e, lifetime in world:query(ct.Lifetime) do
|
||||||
|
if os.clock() > lifetime.created + lifetime.duration then
|
||||||
|
world:add(e, ct.Destroy)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,9 +12,13 @@ return function(world: types.World, dt: number)
|
||||||
|
|
||||||
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 not character then
|
||||||
if not character.Parent then
|
continue
|
||||||
world:set(entity, ct.Renderable, character)
|
|
||||||
end
|
end
|
||||||
|
if not character.Parent then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
world:set(entity, ct.Renderable, character)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local ct = require(ReplicatedStorage.components)
|
local ct = require(ReplicatedStorage.components)
|
||||||
return function(world, dt)
|
local types = require(ReplicatedStorage.types)
|
||||||
for e, poison, health in world:query(ct.Poison, ct.Health) do
|
local jecs = require(ReplicatedStorage.ecs)
|
||||||
local health_after_tick = health - poison * dt * 0.05
|
|
||||||
if health_after_tick < 0 then
|
return function(world: types.World, dt: number)
|
||||||
world:remove(e, ct.Health)
|
for e, poison_tick in world:query(ct.Poison, jecs.pair(jecs.ChildOf, jecs.w)) do
|
||||||
|
local tgt = world:target(e, jecs.ChildOf)
|
||||||
|
local health = world:get(tgt, ct.Health)
|
||||||
|
if not health then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
world:set(e, ct.Health, health_after_tick)
|
|
||||||
|
if math.random() < 1 / 60 / 1 and health > 1 then
|
||||||
|
world:set(tgt, ct.Health, health - 1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
--!strict
|
||||||
|
local Players = game:GetService("Players")
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local types = require("../../ReplicatedStorage/types")
|
local ct = require(ReplicatedStorage.components)
|
||||||
local ct = require("../../ReplicatedStorage/components")
|
local components = ct :: { [string]: jecs.Entity }
|
||||||
|
local remotes = require(ReplicatedStorage.remotes)
|
||||||
local jecs = require(ReplicatedStorage.ecs)
|
local jecs = require(ReplicatedStorage.ecs)
|
||||||
local remotes = require("../../ReplicatedStorage/remotes")
|
local collect = require(ReplicatedStorage.collect)
|
||||||
local components = ct :: {[string]: jecs.Entity }
|
local ty = require(ReplicatedStorage.types)
|
||||||
|
|
||||||
return function(world: ty.World)
|
return function(world: ty.World)
|
||||||
|
|
||||||
|
@ -19,9 +22,10 @@ return function(world: ty.World)
|
||||||
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 = assert(world:get(component, jecs.Name), "Invalid component")
|
||||||
if components[name] == nil then
|
if components[name] == nil then
|
||||||
continue
|
error("Invalid component:"..name)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
storages[component] = {}
|
storages[component] = {}
|
||||||
|
@ -32,29 +36,30 @@ return function(world: ty.World)
|
||||||
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
|
error("Invalid component")
|
||||||
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)
|
||||||
if not components[name] then
|
if not components[name] then
|
||||||
|
-- error("Invalid component")
|
||||||
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)
|
||||||
|
@ -64,51 +69,55 @@ return function(world: ty.World)
|
||||||
|
|
||||||
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 requested_snapshots = collect(remotes.request_snapshot.OnServerEvent)
|
||||||
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 }
|
||||||
|
|
||||||
|
-- In the future maybe it should be requested by the player instead when they
|
||||||
|
-- are ready to receive the replication. Otherwise streaming could be complicated
|
||||||
|
-- with intances references being nil.
|
||||||
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 = {}, {}
|
||||||
|
@ -126,7 +135,7 @@ return function(world: ty.World)
|
||||||
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_map[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
|
||||||
|
|
||||||
|
@ -135,10 +144,18 @@ return function(world: ty.World)
|
||||||
|
|
||||||
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)] = {
|
local ser_id: string = nil :: any
|
||||||
set = if set_n > 0 then set else nil,
|
|
||||||
values = if set_n > 0 then set_values else nil,
|
if jecs.IS_PAIR(component) then
|
||||||
}
|
ser_id = `{jecs.pair_first(world, component)},{jecs.pair_first(world, component)}`
|
||||||
|
else
|
||||||
|
ser_id = tostring(component)
|
||||||
|
end
|
||||||
|
|
||||||
|
snapshot_lazy[ser_id] = {
|
||||||
|
set = if set_n > 0 then set else nil,
|
||||||
|
values = if set_n > 0 then set_values else nil,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -176,7 +193,15 @@ return function(world: ty.World)
|
||||||
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)] = {
|
local ser_id: string = nil :: any
|
||||||
|
|
||||||
|
if jecs.IS_PAIR(component) then
|
||||||
|
ser_id = `{jecs.pair_first(world, component)},{jecs.pair_second(world, component)}`
|
||||||
|
else
|
||||||
|
ser_id = tostring(component)
|
||||||
|
end
|
||||||
|
|
||||||
|
snapshot[ser_id] = {
|
||||||
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
|
||||||
|
|
103
docs/api/jecs.md
103
docs/api/jecs.md
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Jecs. Just an Entity Component System.
|
Jecs. Just an Entity Component System.
|
||||||
|
|
||||||
# Properties
|
# Members
|
||||||
|
|
||||||
## World
|
## World
|
||||||
```luau
|
```luau
|
||||||
|
@ -12,27 +12,93 @@ A world is a container of all ECS data. Games can have multiple worlds but compo
|
||||||
|
|
||||||
## Wildcard
|
## Wildcard
|
||||||
```luau
|
```luau
|
||||||
jecs.Wildcard: Entity
|
jecs.Wildcard: Id
|
||||||
```
|
```
|
||||||
Builtin component type. This ID is used for wildcard queries.
|
Builtin component type. This ID is used for wildcard queries.
|
||||||
|
|
||||||
## Component
|
## Component
|
||||||
```luau
|
```luau
|
||||||
jecs.Component: Entity
|
jecs.Component: Id
|
||||||
```
|
```
|
||||||
Builtin component type. Every ID created with [world:component()](world.md#component()) has this type added to it. This is meant for querying every component ID.
|
Builtin component type. Every ID created with [world:component()](world.md#component()) has this type added to it. This is meant for querying every component ID.
|
||||||
|
|
||||||
## ChildOf
|
## ChildOf
|
||||||
```luau
|
```luau
|
||||||
jecs.ChildOf: Entity
|
jecs.ChildOf: Id
|
||||||
```
|
```
|
||||||
Builtin component type. This ID is for creating parent-child hierarchies.
|
Builtin component type. This ID is for creating parent-child hierarchies.
|
||||||
|
|
||||||
|
## OnAdd
|
||||||
|
|
||||||
|
```luau
|
||||||
|
jecs.OnAdd: Id
|
||||||
|
```
|
||||||
|
Builtin component type. This ID is for setting up a callback that is invoked when an instance of a component is added.
|
||||||
|
|
||||||
|
## OnRemove
|
||||||
|
|
||||||
|
```luau
|
||||||
|
jecs.OnRemove: Id
|
||||||
|
```
|
||||||
|
|
||||||
|
Builtin component type. This ID is for setting up a callback that is invoked when an instance of a component is removed.
|
||||||
|
|
||||||
|
## OnChange
|
||||||
|
|
||||||
|
```luau
|
||||||
|
jecs.OnChange: Id
|
||||||
|
```
|
||||||
|
|
||||||
|
Builtin component type. This ID is for setting up a callback that is invoked when an instance of a component is changed.
|
||||||
|
|
||||||
|
## Exclusive
|
||||||
|
|
||||||
|
```lua
|
||||||
|
jecs.Exclusive: Id
|
||||||
|
```
|
||||||
|
|
||||||
|
Builtin component type. This ID is for encoding that an ID is Exclusive meaning that an entity can never have more than one target for that exclusive relation.
|
||||||
|
|
||||||
|
:::code-group
|
||||||
|
```luau [luau]
|
||||||
|
local ChildOf = world:entity()
|
||||||
|
world:add(ChildOf, jecs.Exclusive)
|
||||||
|
|
||||||
|
local pop = world:entity()
|
||||||
|
local dad = world:entity()
|
||||||
|
local kid = world:entity()
|
||||||
|
|
||||||
|
world:add(kid, pair(ChildOf, dad))
|
||||||
|
print(world:target(kid, ChildOf, 0) == dad)
|
||||||
|
world:add(kid, pair(ChildOf, pop))
|
||||||
|
print(world:target(kid, ChildOf, 1) == dad) -- If ChildOf was not exclusive this would have been true
|
||||||
|
print(world:target(kid, ChildOf, 0) == pop)
|
||||||
|
|
||||||
|
-- Output:
|
||||||
|
-- true
|
||||||
|
-- false
|
||||||
|
-- true
|
||||||
|
```
|
||||||
|
|
||||||
|
:::info
|
||||||
|
By default, jecs.ChildOf is already an exclusive relationship and this is just a demonstration of how to use it.
|
||||||
|
In some cases you can use Exclusive relationships as a performance optimization as you can guarantee there will only be one target, therefore
|
||||||
|
retrieving the data from a wildcard pair with that exclusive relationship can be deterministic.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Name
|
||||||
|
```luau
|
||||||
|
jecs.Name: Id
|
||||||
|
```
|
||||||
|
Builtin component type. This ID is for naming components, but realistically you could use any component to do that.
|
||||||
|
|
||||||
## Rest
|
## Rest
|
||||||
```luau
|
```luau
|
||||||
jecs.Rest: Entity
|
jecs.Rest: Id
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Builtin component type. This ID is simply for denoting the end of the range for builtin component IDs.
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
|
|
||||||
## pair()
|
## pair()
|
||||||
|
@ -48,3 +114,30 @@ function jecs.pair(
|
||||||
While relationship pairs can be used as components and have data associated with an ID, they cannot be used as entities. Meaning you cannot add components to a pair as the source of a binding.
|
While relationship pairs can be used as components and have data associated with an ID, they cannot be used as entities. Meaning you cannot add components to a pair as the source of a binding.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## pair_first()
|
||||||
|
```luau
|
||||||
|
function jecs.pair_first(
|
||||||
|
pair: Id, -- A full pair ID encoded using a relation-target pair.
|
||||||
|
): Entity -- The ID of the first element. Returns 0 if the ID is not alive.
|
||||||
|
```
|
||||||
|
Returns the first element (the relation part) of a pair ID.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```luau
|
||||||
|
local Likes = world:component()
|
||||||
|
local alice = world:entity()
|
||||||
|
local bob = world:entity()
|
||||||
|
|
||||||
|
local pair_id = pair(Likes, alice)
|
||||||
|
local relation = jecs.pair_first(pair_id)
|
||||||
|
print(relation == Likes) -- true
|
||||||
|
```
|
||||||
|
|
||||||
|
## pair_second()
|
||||||
|
```luau
|
||||||
|
function jecs.pair_second(
|
||||||
|
pair: Id, -- A full pair ID encoded using a relation-target pair.
|
||||||
|
): Entity -- The ID of the second element. Returns 0 if the ID is not alive.
|
||||||
|
```
|
||||||
|
Returns the second element (the target part) of a pair ID.
|
||||||
|
|
196
docs/api/observers.md
Executable file
196
docs/api/observers.md
Executable file
|
@ -0,0 +1,196 @@
|
||||||
|
# Observers
|
||||||
|
|
||||||
|
The observers addon extends the World with signal-based reactivity and query-based observers. This addon provides a more ergonomic way to handle component lifecycle events and query changes.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The observers addon is included with jecs and can be imported directly:
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local jecs = require(path/to/jecs)
|
||||||
|
local observers_add = require(path/to/jecs/addons/observers)
|
||||||
|
|
||||||
|
local world = observers_add(jecs.world())
|
||||||
|
```
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### added
|
||||||
|
|
||||||
|
Registers a callback that is invoked when a component is added to any entity.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:added<T>(
|
||||||
|
component: Id<T>,
|
||||||
|
callback: (entity: Entity, id: Id<T>, value: T?) -> ()
|
||||||
|
): () -> () -- Returns an unsubscribe function
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `component` - The component ID to listen for additions
|
||||||
|
- `callback` - Function called when component is added, receives entity, component ID, and value
|
||||||
|
|
||||||
|
**Returns:** An unsubscribe function that removes the listener when called
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```luau
|
||||||
|
local Health = world:component() :: jecs.Entity<number>
|
||||||
|
|
||||||
|
local unsubscribe = world:added(Health, function(entity, id, value)
|
||||||
|
print("Health component added to entity", entity, "with value", value)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Later, to stop listening:
|
||||||
|
unsubscribe()
|
||||||
|
```
|
||||||
|
|
||||||
|
### removed
|
||||||
|
|
||||||
|
Registers a callback that is invoked when a component is removed from any entity.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:removed<T>(
|
||||||
|
component: Id<T>,
|
||||||
|
callback: (entity: Entity, id: Id<T>) -> ()
|
||||||
|
): () -> () -- Returns an unsubscribe function
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `component` - The component ID to listen for removals
|
||||||
|
- `callback` - Function called when component is removed, receives entity and component ID
|
||||||
|
|
||||||
|
**Returns:** An unsubscribe function that removes the listener when called
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```luau
|
||||||
|
local Health = world:component() :: jecs.Entity<number>
|
||||||
|
|
||||||
|
local unsubscribe = world:removed(Health, function(entity, id)
|
||||||
|
print("Health component removed from entity", entity)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
### changed
|
||||||
|
|
||||||
|
Registers a callback that is invoked when a component's value is changed on any entity.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:changed<T>(
|
||||||
|
component: Id<T>,
|
||||||
|
callback: (entity: Entity, id: Id<T>, value: T) -> ()
|
||||||
|
): () -> () -- Returns an unsubscribe function
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `component` - The component ID to listen for changes
|
||||||
|
- `callback` - Function called when component value changes, receives entity, component ID, and new value
|
||||||
|
|
||||||
|
**Returns:** An unsubscribe function that removes the listener when called
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```luau
|
||||||
|
local Health = world:component() :: jecs.Entity<number>
|
||||||
|
|
||||||
|
local unsubscribe = world:changed(Health, function(entity, id, value)
|
||||||
|
print("Health changed to", value, "for entity", entity)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
### observer
|
||||||
|
|
||||||
|
Creates a query-based observer that triggers when entities match or stop matching a query.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:observer<T...>(
|
||||||
|
query: Query<T...>,
|
||||||
|
callback: ((entity: Entity, id: Id, value: any?) -> ())?
|
||||||
|
): () -> () -> Entity -- Returns an iterator function
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `query` - The query to observe for changes
|
||||||
|
- `callback` - Optional function called when entities match the query
|
||||||
|
|
||||||
|
**Returns:** An iterator function that returns entities that matched the query since last iteration
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```luau
|
||||||
|
local Position = world:component() :: jecs.Id<Vector3>
|
||||||
|
local Velocity = world:component() :: jecs.Id<Vector3>
|
||||||
|
|
||||||
|
local moving_entities = world:observer(
|
||||||
|
world:query(Position, Velocity),
|
||||||
|
function(entity, id, value)
|
||||||
|
print("Entity", entity, "started moving")
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
-- In your game loop:
|
||||||
|
for entity in moving_entities() do
|
||||||
|
-- Process newly moving entities
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### monitor
|
||||||
|
|
||||||
|
Creates a query-based monitor that triggers when entities are added to or removed from a query.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:monitor<T...>(
|
||||||
|
query: Query<T...>,
|
||||||
|
callback: ((entity: Entity, id: Id, value: any?) -> ())?
|
||||||
|
): () -> () -> Entity -- Returns an iterator function
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `query` - The query to monitor for additions/removals
|
||||||
|
- `callback` - Optional function called when entities are added or removed from the query
|
||||||
|
|
||||||
|
**Returns:** An iterator function that returns entities that were added or removed since last iteration
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```luau
|
||||||
|
local Health = world:component() :: jecs.Id<number>
|
||||||
|
|
||||||
|
local health_changes = world:monitor(
|
||||||
|
world:query(Health),
|
||||||
|
function(entity, id, value)
|
||||||
|
print("Health component changed for entity", entity)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
-- In your game loop:
|
||||||
|
for entity in health_changes() do
|
||||||
|
-- Process entities with health changes
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Patterns
|
||||||
|
|
||||||
|
### Component Lifecycle Tracking
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local Player = world:component()
|
||||||
|
local Health = world:component() :: jecs.Id<number>
|
||||||
|
|
||||||
|
-- Track when players are created
|
||||||
|
world:added(Player, function(entity, id, instance)
|
||||||
|
instance:SetAttribute("entityid", entity)
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:removed(Player, function(entity, id)
|
||||||
|
world:add(entity, Destroy) -- process its deletion later!
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
- **Signal listeners** are called immediately when components are added/removed/changed
|
||||||
|
- **Query observers** cache the query for better performance
|
||||||
|
- **Multiple listeners** for the same component are supported and called in registration order
|
||||||
|
- **Unsubscribe functions** should be called when listeners are no longer needed to prevent memory leaks
|
||||||
|
- **Observer iterators** should be called regularly to clear the internal buffer
|
||||||
|
|
||||||
|
## Integration with Built-in Hooks
|
||||||
|
|
||||||
|
The observers addon integrates with the built-in component hooks (`OnAdd`, `OnRemove`, `OnChange`). If a component already has these hooks configured, the observers addon will preserve them and call both the original hook and any registered signal listeners.
|
|
@ -4,13 +4,38 @@ A World contains entities which have components. The World is queryable and can
|
||||||
|
|
||||||
# Methods
|
# Methods
|
||||||
|
|
||||||
## iter
|
## cached
|
||||||
|
|
||||||
Returns an iterator that can be used to iterate over the query.
|
Returns a cached version of the query. This is useful if you want to create a query that you can iterate multiple times.
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function Query:iter(): () -> (Entity, ...)
|
function Query:cached(): Query -- Returns the cached Query
|
||||||
```
|
```
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
local lerps = world:query(Lerp):cached() -- Ensure that you cache this outside a system so you do not create a new cache for a query every frame
|
||||||
|
|
||||||
|
local function system(dt)
|
||||||
|
for entity, lerp in lerps do
|
||||||
|
-- Do something
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
const lerps = world.query(Lerp).cached()
|
||||||
|
|
||||||
|
function system(dt) {
|
||||||
|
for (const [entity, lerp] of lerps) {
|
||||||
|
// Do something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## with
|
## with
|
||||||
|
|
||||||
|
@ -83,15 +108,13 @@ Example:
|
||||||
|
|
||||||
```luau [luau]
|
```luau [luau]
|
||||||
for i, archetype in world:query(Position, Velocity):archetypes() do
|
for i, archetype in world:query(Position, Velocity):archetypes() do
|
||||||
local columns = archetype.columns
|
local field = archetype.columns_map
|
||||||
local field = archetype.records
|
local positions = field[Position]
|
||||||
|
local velocities = field[Velocity]
|
||||||
local P = field[Position]
|
|
||||||
local V = field[Velocity]
|
|
||||||
|
|
||||||
for row, entity in archetype.entities do
|
for row, entity in archetype.entities do
|
||||||
local position = columns[P][row]
|
local position = positions[row]
|
||||||
local velocity = columns[V][row]
|
local velocity = velocities[row]
|
||||||
-- Do something
|
-- Do something
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -101,10 +124,27 @@ end
|
||||||
This function is meant for people who want to really customize their query behaviour at the archetype-level
|
This function is meant for people who want to really customize their query behaviour at the archetype-level
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## cached
|
## iter
|
||||||
|
In most cases, you can iterate over queries directly using `for entity, ... in query do`. The `:iter()` method is mainly useful if you are on the old solver, to get types for the returned values.
|
||||||
Returns a cached version of the query. This is useful if you want to iterate over the same query multiple times.
|
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function Query:cached(): Query -- Returns the cached Query
|
function Query:iter(): () -> (Entity, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
```luau [luau]
|
||||||
|
local query = world:query(Position, Velocity)
|
||||||
|
|
||||||
|
-- Direct iteration (recommended)
|
||||||
|
for entity, position, velocity in query do
|
||||||
|
-- Process entity
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Using explicit iterator (when needed for the old solver)
|
||||||
|
local iterator = query:iter()
|
||||||
|
for entity, position, velocity in iterator do
|
||||||
|
-- Process entity
|
||||||
|
end
|
||||||
```
|
```
|
||||||
|
|
|
@ -6,7 +6,7 @@ A World contains entities which have components. The World is queryable and can
|
||||||
|
|
||||||
## new
|
## new
|
||||||
|
|
||||||
`World` utilizes a class, meaning JECS allows you to create multiple worlds.
|
`World` utilizes a class, meaning jecs allows you to create multiple worlds.
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function World.new(): World
|
function World.new(): World
|
||||||
|
@ -55,12 +55,12 @@ const entity = world.entity();
|
||||||
|
|
||||||
## component
|
## component
|
||||||
|
|
||||||
Creates a new component. Do note components are entities as well, meaning JECS allows you to add other components onto them.
|
Creates a new component. Do note components are entities as well, meaning jecs allows you to add other components onto them.
|
||||||
|
|
||||||
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 componen.
|
function World:component<T>(): Entity<T> -- The new component.
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
@ -241,7 +241,10 @@ print(world.get(Entity, Health));
|
||||||
// 100
|
// 100
|
||||||
// 50
|
// 50
|
||||||
```
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::info
|
||||||
|
`world:set(entity, component, value)` propagates that a change has happened for thes component on this entity, while mutating a value directly would not.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## query
|
## query
|
||||||
|
@ -289,10 +292,52 @@ If the index is larger than the total number of instances the entity has for the
|
||||||
```luau
|
```luau
|
||||||
function World:target(
|
function World:target(
|
||||||
entity: Entity, -- The entity
|
entity: Entity, -- The entity
|
||||||
relation: Entity, -- The relationship between the entity and the target
|
relation: Id, -- The relationship between the entity and the target
|
||||||
nth: number, -- The index
|
nth: number, -- The index
|
||||||
): Entity? -- The target for the relationship at the specified index.
|
): Id? -- The target for the relationship at the specified index.
|
||||||
```
|
```
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
local function timers_count(world: types.World)
|
||||||
|
local timers = world
|
||||||
|
:query(jecs.pair(ct.Timer, jecs.w))
|
||||||
|
:without(ct.Destroy)
|
||||||
|
:cached()
|
||||||
|
|
||||||
|
return function(_, dt: number)
|
||||||
|
for entity in timers do
|
||||||
|
local index = 0
|
||||||
|
local nth = world:target(entity, ct.Timer, index)
|
||||||
|
while nth do
|
||||||
|
local timer = world:get(entity, jecs.pair(ct.Timer, nth))
|
||||||
|
local elapsed = timer.elapsed + dt
|
||||||
|
if elapsed >= timer.duration then
|
||||||
|
world:add(entity, ct.Destroy)
|
||||||
|
end
|
||||||
|
timer.elapsed = elapsed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
const entity = world.entity();
|
||||||
|
print(world.contains(entity));
|
||||||
|
print(world.contains(1));
|
||||||
|
print(world.contains(2));
|
||||||
|
|
||||||
|
// Outputs:
|
||||||
|
// true
|
||||||
|
// true
|
||||||
|
// false
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
## parent
|
## parent
|
||||||
|
|
||||||
|
@ -355,9 +400,9 @@ print(world.contains(2));
|
||||||
Removes a component (ID) from an entity
|
Removes a component (ID) from an entity
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function World:remove(
|
function World:remove<T>(
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
component: Entity<T>
|
component: Id<T>
|
||||||
): void
|
): void
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -458,20 +503,20 @@ Useful when you only need the entity for a specific ID and you want to avoid cre
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function World:each(
|
function World:each(
|
||||||
id: Entity -- The component ID
|
component: Id -- The component ID
|
||||||
): () -> Entity
|
): () -> Entity
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
::: code-group
|
::: code-group
|
||||||
```luau [luau]
|
```luau [luau]
|
||||||
local id = world:entity()
|
local id = world:component()
|
||||||
for entity in world:each(id) do
|
for entity in world:each(id) do
|
||||||
-- Do something
|
-- Do something
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
```ts [typescript]
|
```ts [typescript]
|
||||||
const id = world.entity();
|
const id = world.component();
|
||||||
for (const entity of world.each(id)) {
|
for (const entity of world.each(id)) {
|
||||||
// Do something
|
// Do something
|
||||||
}
|
}
|
||||||
|
@ -500,6 +545,122 @@ Enforces a check for entities to be created within a desired range.
|
||||||
```luau
|
```luau
|
||||||
function World:range(
|
function World:range(
|
||||||
range_begin: number -- The starting point,
|
range_begin: number -- The starting point,
|
||||||
range_begin: number? -- The end point (optional)
|
range_end: number? -- The end point (optional)
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
```luau [luau]
|
||||||
|
world:range(1000, 5000) -- Entities will be created with IDs 1000-5000
|
||||||
|
|
||||||
|
local entity = world:entity()
|
||||||
|
print(entity) -- Will be >= 1000 and < 5000
|
||||||
|
```
|
||||||
|
```ts [typescript]
|
||||||
|
world.range(1000, 5000) // Entities will be created with IDs 1000-5000
|
||||||
|
|
||||||
|
const entity = world.entity()
|
||||||
|
print(entity) // Will be >= 1000 and < 5000
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
## parent
|
||||||
|
|
||||||
|
Gets the parent entity of the specified entity using the built-in `ChildOf` relationship.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:parent(
|
||||||
|
entity: Entity
|
||||||
|
): Entity? -- Returns the parent entity or nil if no parent
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
```luau [luau]
|
||||||
|
local parent = world:entity()
|
||||||
|
local child = world:entity()
|
||||||
|
|
||||||
|
world:add(child, pair(jecs.ChildOf, parent))
|
||||||
|
|
||||||
|
local retrieved_parent = world:parent(child)
|
||||||
|
print(retrieved_parent == parent) -- true
|
||||||
|
```
|
||||||
|
```ts [typescript]
|
||||||
|
const parent = world.entity()
|
||||||
|
const child = world.entity()
|
||||||
|
|
||||||
|
world.add(child, pair(jecs.ChildOf, parent))
|
||||||
|
|
||||||
|
const retrievedParent = world.parent(child)
|
||||||
|
print(retrievedParent === parent) // true
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
## contains
|
||||||
|
|
||||||
|
Checks if an entity exists and is alive in the world.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:contains(
|
||||||
|
entity: Entity
|
||||||
|
): boolean
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
```luau [luau]
|
||||||
|
local entity = world:entity()
|
||||||
|
print(world:contains(entity)) -- true
|
||||||
|
|
||||||
|
world:delete(entity)
|
||||||
|
print(world:contains(entity)) -- false
|
||||||
|
```
|
||||||
|
```ts [typescript]
|
||||||
|
const entity = world.entity()
|
||||||
|
print(world.contains(entity)) // true
|
||||||
|
|
||||||
|
world.delete(entity)
|
||||||
|
print(world.contains(entity)) // false
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
## exists
|
||||||
|
|
||||||
|
Checks if the entity ID exists regardless of whether it is alive or not. Useful to know if the ID is occupied in the entity index.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:exists(
|
||||||
|
entity: Entity
|
||||||
|
): boolean
|
||||||
|
```
|
||||||
|
|
||||||
|
## cleanup
|
||||||
|
|
||||||
|
Cleans up deleted entities and their associated data. This is automatically called by jecs, but can be called manually if needed.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function World:cleanup(): void
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
```luau [luau]
|
||||||
|
local entity = world:entity()
|
||||||
|
world:delete(entity)
|
||||||
|
|
||||||
|
-- Cleanup is usually automatic, but can be called manually
|
||||||
|
world:cleanup()
|
||||||
|
```
|
||||||
|
```ts [typescript]
|
||||||
|
const entity = world.entity()
|
||||||
|
world.delete(entity)
|
||||||
|
|
||||||
|
// Cleanup is usually automatic, but can be called manually
|
||||||
|
world.cleanup()
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
|
@ -27,8 +27,8 @@ local jecs = require(path/to/jecs)
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
```
|
```
|
||||||
```typescript [typescript]
|
```typescript [typescript]
|
||||||
import { World } from "@rbxts/jecs"
|
import * as jecs from "@rbxts/jecs"
|
||||||
const world = new World()
|
const world = jecs.world()
|
||||||
// creates a new entity with no components and returns its identifier
|
// creates a new entity with no components and returns its identifier
|
||||||
const entity = world.entity()
|
const entity = world.entity()
|
||||||
|
|
||||||
|
@ -156,6 +156,13 @@ world.set(Transform, OnChange, (entity, id, data) => {
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::info
|
||||||
|
Children are cleaned up before parents
|
||||||
|
When a parent and its children are deleted, OnRemove hooks will be invoked for children first, under the condition that there are no cycles in the relationship graph of the deleted entities. This order is maintained for any relationship that has the (OnDeleteTarget, Delete) trait (see Component Traits for more details).
|
||||||
|
|
||||||
|
When an entity graph contains cycles, order is undefined. This includes cycles that can be formed using different relationships.
|
||||||
|
:::
|
||||||
|
|
||||||
### Cleanup Traits
|
### Cleanup Traits
|
||||||
|
|
||||||
When entities that are used as tags, components, relationships or relationship targets are deleted, cleanup traits ensure that the store does not contain any dangling references. Any cleanup policy provides this guarantee, so while they are configurable, games cannot configure traits that allows for dangling references.
|
When entities that are used as tags, components, relationships or relationship targets are deleted, cleanup traits ensure that the store does not contain any dangling references. Any cleanup policy provides this guarantee, so while they are configurable, games cannot configure traits that allows for dangling references.
|
||||||
|
@ -166,15 +173,22 @@ This is what cleanup traits are for: to specify which action needs to be execute
|
||||||
|
|
||||||
To configure a cleanup policy for an entity, a `(Condition, Action)` pair can be added to it. If no policy is specified, the default cleanup action (`Remove`) is performed.
|
To configure a cleanup policy for an entity, a `(Condition, Action)` pair can be added to it. If no policy is specified, the default cleanup action (`Remove`) is performed.
|
||||||
|
|
||||||
There are two cleanup actions:
|
#### Cleanup Traits Summary
|
||||||
|
|
||||||
- `Remove`: removes instances of the specified (component) id from all entities (default)
|
| Condition | Action | Description | Use Case |
|
||||||
- `Delete`: deletes all entities with specified id
|
|-----------|--------|-------------|----------|
|
||||||
|
| `OnDelete` | `Remove` | Removes the component from all entities when the component is deleted | Default behavior, safe cleanup |
|
||||||
|
| `OnDelete` | `Delete` | Deletes all entities that have the component when the component is deleted | Cascading deletion, dangerous |
|
||||||
|
| `OnDeleteTarget` | `Remove` | Removes the relationship from all entities when the target is deleted | Safe relationship cleanup |
|
||||||
|
| `OnDeleteTarget` | `Delete` | Deletes all entities that have the relationship when the target is deleted | Hierarchical deletion (e.g., parent-child) |
|
||||||
|
|
||||||
There are two cleanup conditions:
|
**Cleanup Actions:**
|
||||||
|
- `Remove`: removes instances of the specified (component) id from all entities (default)
|
||||||
|
- `Delete`: deletes all entities with specified id
|
||||||
|
|
||||||
- `OnDelete`: the component, tag or relationship is deleted
|
**Cleanup Conditions:**
|
||||||
- `OnDeleteTarget`: a target used with the relationship is deleted
|
- `OnDelete`: the component, tag or relationship is deleted
|
||||||
|
- `OnDeleteTarget`: a target used with the relationship is deleted
|
||||||
|
|
||||||
#### (OnDelete, Remove)
|
#### (OnDelete, Remove)
|
||||||
::: code-group
|
::: code-group
|
||||||
|
@ -285,9 +299,10 @@ jecs.world() -- Position gets registered here
|
||||||
```
|
```
|
||||||
|
|
||||||
```typescript [typescript]
|
```typescript [typescript]
|
||||||
|
import { world } from "@rbxts/jecs"
|
||||||
const Position = jecs.component<Vector3>();
|
const Position = jecs.component<Vector3>();
|
||||||
|
|
||||||
new World() // Position gets registered here
|
world() // Position gets registered here
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
@ -301,9 +316,11 @@ jecs.world() -- Position gets registered here with its name "Position"
|
||||||
```
|
```
|
||||||
|
|
||||||
```typescript [typescript]
|
```typescript [typescript]
|
||||||
|
import { world } from "@rbxts/jecs"
|
||||||
|
|
||||||
jecs.meta(Position, jecs.Name, "Position")
|
jecs.meta(Position, jecs.Name, "Position")
|
||||||
|
|
||||||
new World() // Position gets registered here with its name "Position"
|
world() // Position gets registered here with its name "Position"
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
@ -632,7 +649,7 @@ world:set(e, pair(Eats, Apples), { amount = 1 })
|
||||||
world:set(e, pair(Begin, Position), Vector3.new(0, 0, 0))
|
world:set(e, pair(Begin, Position), Vector3.new(0, 0, 0))
|
||||||
world:set(e, pair(End, Position), Vector3.new(10, 20, 30))
|
world:set(e, pair(End, Position), Vector3.new(10, 20, 30))
|
||||||
|
|
||||||
world:add(e, jecs.ChildOf, Position)
|
world:add(e, pair(jecs.ChildOf, Position))
|
||||||
|
|
||||||
```
|
```
|
||||||
```typescript [typescript]
|
```typescript [typescript]
|
||||||
|
@ -648,7 +665,7 @@ world.set(e, pair(Eats, Apples), { amount: 1 })
|
||||||
world.set(e, pair(Begin, Position), new Vector3(0, 0, 0))
|
world.set(e, pair(Begin, Position), new Vector3(0, 0, 0))
|
||||||
world.set(e, pair(End, Position), new Vector3(10, 20, 30))
|
world.set(e, pair(End, Position), new Vector3(10, 20, 30))
|
||||||
|
|
||||||
world.add(e, jecs.ChildOf, Position)
|
world.add(e, pair(jecs.ChildOf, Position))
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
@ -695,3 +712,7 @@ To improve the speed of evaluating queries, Jecs has indices that store all arch
|
||||||
While registering an archetype for a relationship index is not more expensive than registering an archetype for a regular index, an archetype with relationships has to also register itself with the appropriate wildcard indices for its relationships. For example, an archetype with relationship `pair(Likes, Apples)` registers itself with the `pair(Likes, Apples)`, `pair(Likes, jecs.Wildcard)` and `pair(jecs.Wildcard, Apples)` indices. For this reason, creating new archetypes with relationships has a higher overhead than an archetype without relationships.
|
While registering an archetype for a relationship index is not more expensive than registering an archetype for a regular index, an archetype with relationships has to also register itself with the appropriate wildcard indices for its relationships. For example, an archetype with relationship `pair(Likes, Apples)` registers itself with the `pair(Likes, Apples)`, `pair(Likes, jecs.Wildcard)` and `pair(jecs.Wildcard, Apples)` indices. For this reason, creating new archetypes with relationships has a higher overhead than an archetype without relationships.
|
||||||
|
|
||||||
This page takes wording and terminology directly from Flecs, the first ECS with full support for [Entity Relationships](https://www.flecs.dev/flecs/md_docs_2Relationships.html).
|
This page takes wording and terminology directly from Flecs, the first ECS with full support for [Entity Relationships](https://www.flecs.dev/flecs/md_docs_2Relationships.html).
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [API Reference](../api/jecs.md) - Complete API documentation
|
||||||
|
|
|
@ -49,8 +49,8 @@ A tool for inspecting entity lifetimes
|
||||||
|
|
||||||
### Helpers
|
### Helpers
|
||||||
|
|
||||||
#### [jecs_observers](https://github.com/Ukendio/jecs/blob/main/addons/observers.luau)
|
#### [jecs_ob](https://github.com/Ukendio/jecs/blob/main/addons/ob.luau)
|
||||||
Observers for queries and signals for components
|
Observers & Monitors for queries
|
||||||
|
|
||||||
### [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
|
||||||
|
|
19
jecs.d.ts
vendored
19
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: { [K in keyof T]: Column<T[K]> }
|
columns_map: Record<Id, Column<T[number]>>
|
||||||
};
|
};
|
||||||
|
|
||||||
type Iter<T extends unknown[]> = IterableFunction<LuaTuple<[Entity, ...T]>>;
|
type Iter<T extends unknown[]> = IterableFunction<LuaTuple<[Entity, ...T]>>;
|
||||||
|
@ -105,7 +105,7 @@ export class World {
|
||||||
/**
|
/**
|
||||||
* Creates a new World.
|
* Creates a new World.
|
||||||
*/
|
*/
|
||||||
constructor();
|
private constructor();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enforces a check for entities to be created within a desired range.
|
* Enforces a check for entities to be created within a desired range.
|
||||||
|
@ -156,8 +156,8 @@ export class World {
|
||||||
* @param hook The hook to install.
|
* @param hook The hook to install.
|
||||||
* @param value The hook callback.
|
* @param value The hook callback.
|
||||||
*/
|
*/
|
||||||
set<T>(component: Entity<T>, hook: StatefulHook, value: (e: Entity<T>, id: Id<T>, data: T) => void): void;
|
set<T>(component: Entity<T>, hook: StatefulHook, value: (e: Entity, id: Id<T>, data: T) => void): void;
|
||||||
set<T>(component: Entity<T>, hook: StatelessHook, value: (e: Entity<T>, id: Id<T>) => void): void;
|
set<T>(component: Entity<T>, hook: StatelessHook, value: (e: Entity, id: Id<T>) => void): void;
|
||||||
/**
|
/**
|
||||||
* Assigns a value to a component on the given entity.
|
* Assigns a value to a component on the given entity.
|
||||||
* @param entity The target entity.
|
* @param entity The target entity.
|
||||||
|
@ -247,8 +247,14 @@ 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 component<T>(): Entity<T>;
|
export function component<T>(): Entity<T>;
|
||||||
|
|
||||||
export function tag(): Tag;
|
export function tag(): Tag;
|
||||||
|
@ -301,6 +307,7 @@ export declare const OnAdd: StatefulHook;
|
||||||
export declare const OnRemove: StatelessHook;
|
export declare const OnRemove: StatelessHook;
|
||||||
export declare const OnChange: StatefulHook;
|
export declare const OnChange: StatefulHook;
|
||||||
export declare const ChildOf: Tag;
|
export declare const ChildOf: Tag;
|
||||||
|
export declare const Component: Tag;
|
||||||
export declare const Wildcard: Entity;
|
export declare const Wildcard: Entity;
|
||||||
export declare const w: Entity;
|
export declare const w: Entity;
|
||||||
export declare const OnDelete: Tag;
|
export declare const OnDelete: Tag;
|
||||||
|
@ -308,6 +315,7 @@ export declare const OnDeleteTarget: Tag;
|
||||||
export declare const Delete: Tag;
|
export declare const Delete: Tag;
|
||||||
export declare const Remove: Tag;
|
export declare const Remove: Tag;
|
||||||
export declare const Name: Entity<string>;
|
export declare const Name: Entity<string>;
|
||||||
|
export declare const Exclusive: Tag;
|
||||||
export declare const Rest: Entity;
|
export declare const Rest: Entity;
|
||||||
|
|
||||||
export type ComponentRecord = {
|
export type ComponentRecord = {
|
||||||
|
@ -317,3 +325,6 @@ 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_remove(world: World, entity: Entity, ids: Id[]): void
|
||||||
|
|
2878
mirror.luau
2878
mirror.luau
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.7.2",
|
"version": "0.9.0-rc.8",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "jecs.luau",
|
"main": "jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -2,43 +2,28 @@ 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 observers_add = require("@addons/observers")
|
local FOCUS = test.FOCUS
|
||||||
|
local ob = require("@addons/ob")
|
||||||
|
|
||||||
TEST("addons/observers", function()
|
TEST("addons/observers", function()
|
||||||
local world = observers_add(jecs.world())
|
|
||||||
|
|
||||||
do CASE "Should work even if set after the component has been used"
|
local world = jecs.world()
|
||||||
|
do CASE "monitors should accept pairs"
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
world:set(world:entity(), A, 2)
|
local c = 1
|
||||||
local ran = false
|
ob.monitor(world:query(jecs.pair(A, B)), function (_, event)
|
||||||
world:added(A, function()
|
c += 1
|
||||||
ran = true
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local entity = world:entity()
|
local child = world:entity()
|
||||||
world:set(entity, A, 3)
|
world:add(child, jecs.pair(A, B))
|
||||||
|
CHECK(c == 2)
|
||||||
|
|
||||||
CHECK(ran)
|
world:remove(child, jecs.pair(A, B))
|
||||||
|
CHECK(c == 3)
|
||||||
end
|
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()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
@ -48,11 +33,15 @@ TEST("addons/observers", function()
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
world:observer(world:query(A, B), counter)
|
ob.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)
|
||||||
|
@ -69,7 +58,7 @@ TEST("addons/observers", function()
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
world:observer(world:query(A), counter)
|
ob.observer(world:query(A), counter)
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:set(e, A, false)
|
world:set(e, A, false)
|
||||||
|
@ -89,7 +78,7 @@ TEST("addons/observers", function()
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
world:monitor(world:query(A), counter)
|
ob.monitor(world:query(A), counter)
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:set(e, A, false)
|
world:set(e, A, false)
|
||||||
|
|
548
test/tests.luau
548
test/tests.luau
|
@ -22,9 +22,185 @@ type Entity<T=nil> = jecs.Entity<T>
|
||||||
type Id<T=unknown> = jecs.Id<T>
|
type Id<T=unknown> = jecs.Id<T>
|
||||||
|
|
||||||
local entity_visualiser = require("@tools/entity_visualiser")
|
local entity_visualiser = require("@tools/entity_visualiser")
|
||||||
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
|
||||||
local dwi = entity_visualiser.stringify
|
local dwi = entity_visualiser.stringify
|
||||||
|
|
||||||
|
TEST("ardi", function()
|
||||||
|
local world = jecs.world()
|
||||||
|
local r = world:entity()
|
||||||
|
world:add(r, jecs.pair(jecs.OnDelete, jecs.Delete))
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
local e1 = world:entity()
|
||||||
|
world:add(e, jecs.pair(r, e1))
|
||||||
|
|
||||||
|
world:delete(r)
|
||||||
|
CHECK(not world:contains(e))
|
||||||
|
end)
|
||||||
|
|
||||||
|
TEST("dai", function()
|
||||||
|
local world = jecs.world()
|
||||||
|
local C = world:component()
|
||||||
|
|
||||||
|
world:set(C, jecs.Name, "C")
|
||||||
|
CHECK(world:get(C, jecs.Name) == "C")
|
||||||
|
world:entity(2000)
|
||||||
|
CHECK(world:get(C, jecs.Name) == "C")
|
||||||
|
end)
|
||||||
|
|
||||||
|
TEST("another axen banger", function()
|
||||||
|
-- taken from jecs.luau
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(2000, 3000)
|
||||||
|
|
||||||
|
local e0v1_id = jecs.ECS_COMBINE(1000, 1) -- id can be both within or outside the world's range
|
||||||
|
local e0v1 = world:entity(e0v1_id)
|
||||||
|
assert(world:contains(e0v1)) -- fails
|
||||||
|
end)
|
||||||
|
TEST("Ensure archetype edges get cleaned", function()
|
||||||
|
local A = jecs.component()
|
||||||
|
local B = jecs.component()
|
||||||
|
local world = jecs.world()
|
||||||
|
local edges = world.archetype_edges
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
local r = jecs.record(world, e)
|
||||||
|
|
||||||
|
world:set(e, A, true)
|
||||||
|
world:add(e, A)
|
||||||
|
local arch_a = r.archetype
|
||||||
|
world:set(e, B, true)
|
||||||
|
world:add(e, B)
|
||||||
|
local arch_ab = r.archetype
|
||||||
|
CHECK(edges[arch_a.id][B] == arch_ab)
|
||||||
|
CHECK(edges[arch_ab.id][B] == arch_a)
|
||||||
|
|
||||||
|
world:delete(B)
|
||||||
|
|
||||||
|
CHECK(edges[arch_a.id][B] == nil)
|
||||||
|
CHECK(edges[arch_ab.id][A] == nil)
|
||||||
|
for _ in edges[arch_ab.id] do
|
||||||
|
CHECK(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
world:delete(A)
|
||||||
|
|
||||||
|
CHECK(edges[arch_a.id][B] == nil)
|
||||||
|
CHECK(edges[arch_a.id][A] == nil)
|
||||||
|
for _ in edges[arch_a.id] do
|
||||||
|
CHECK(false)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
TEST("repeated entity cached query", function()
|
||||||
|
local pair = jecs.pair
|
||||||
|
local world = jecs.world()
|
||||||
|
local rel = world:entity()
|
||||||
|
local cmp = world:component()
|
||||||
|
|
||||||
|
local query = world:query(cmp):cached()
|
||||||
|
|
||||||
|
local t1 = world:entity()
|
||||||
|
local p1 = pair(rel, t1)
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
|
||||||
|
world:add(e1, p1)
|
||||||
|
world:set(e1, cmp, true)
|
||||||
|
|
||||||
|
CHECK(query:iter()() == e1)
|
||||||
|
|
||||||
|
world:delete(e1)
|
||||||
|
world:delete(t1)
|
||||||
|
|
||||||
|
local t2 = world:entity()
|
||||||
|
local p2 = pair(rel, t2)
|
||||||
|
|
||||||
|
local e2 = world:entity()
|
||||||
|
|
||||||
|
world:add(e2, p2)
|
||||||
|
world:set(e2, cmp, true)
|
||||||
|
|
||||||
|
CHECK(query:iter()() == e2) -- Fails
|
||||||
|
end)
|
||||||
|
|
||||||
|
TEST("repeated pairs", function()
|
||||||
|
local pair = jecs.pair
|
||||||
|
local world = jecs.world()
|
||||||
|
local rel = world:component() -- Does not error if this is just a tag
|
||||||
|
|
||||||
|
-- Does not happen if we delete manually instead of using this
|
||||||
|
world:add(rel, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
|
||||||
|
local t1 = world:entity()
|
||||||
|
|
||||||
|
local p1 = pair(rel, t1)
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
|
||||||
|
world:set(e1, p1, true)
|
||||||
|
|
||||||
|
CHECK(world:get(e1, p1))
|
||||||
|
CHECK(world:each(p1)() == e1)
|
||||||
|
|
||||||
|
world:delete(t1)
|
||||||
|
|
||||||
|
local t2 = world:entity()
|
||||||
|
local p2 = pair(rel, t2)
|
||||||
|
|
||||||
|
local e2 = world:entity()
|
||||||
|
|
||||||
|
print("-----")
|
||||||
|
world:set(e2, p2, true)
|
||||||
|
|
||||||
|
CHECK(world:get(e2, p2))
|
||||||
|
CHECK(p1 == p2)
|
||||||
|
local count = 0
|
||||||
|
CHECK(world:has(e2, p2))
|
||||||
|
for _ in world:query(p2) do
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
CHECK(count == 1)
|
||||||
|
CHECK(world:each(p2)() == e2) -- Fails
|
||||||
|
end)
|
||||||
|
|
||||||
|
TEST("repro", function()
|
||||||
|
local world = jecs.world()
|
||||||
|
local data = world:component()
|
||||||
|
local relation = world:component()
|
||||||
|
world:add(relation, jecs.pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:set(e2, data, 456)
|
||||||
|
world:add(e2, jecs.pair(relation, e1))
|
||||||
|
world:delete(e1)
|
||||||
|
|
||||||
|
local e1v1 = world:entity()
|
||||||
|
CHECK(ECS_ID(e1v1) == e1::any)
|
||||||
|
local e2v1 = world:entity()
|
||||||
|
CHECK(ECS_ID(e2v1) == e2::any)
|
||||||
|
world:set(e2v1, data, 456)
|
||||||
|
|
||||||
|
CHECK(world:contains(e1v1))
|
||||||
|
CHECK(not world:contains(e2))
|
||||||
|
CHECK(world:contains(e2v1))
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for i,val in world:query(data):iter() do
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
CHECK(count == 1)
|
||||||
|
count = 0
|
||||||
|
print("----")
|
||||||
|
world:add(e2v1, jecs.pair(relation, e1v1))
|
||||||
|
CHECK(world:has(e2v1, jecs.pair(relation, e1v1)))
|
||||||
|
|
||||||
|
for i,val in world:query(data):iter() do
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
print(count)
|
||||||
|
CHECK(count==1)
|
||||||
|
end)
|
||||||
TEST("bulk", function()
|
TEST("bulk", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
@ -42,7 +218,10 @@ TEST("bulk", function()
|
||||||
CHECK(world:get(e, B) == 2)
|
CHECK(world:get(e, B) == 2)
|
||||||
CHECK(world:get(e, C) == 3)
|
CHECK(world:get(e, C) == 3)
|
||||||
|
|
||||||
jecs.bulk_insert(world, e, { D, E, F }, { 4, nil, 5 })
|
jecs.bulk_insert(world, e,
|
||||||
|
{ D, E, F },
|
||||||
|
{ 4, nil, 5 }
|
||||||
|
)
|
||||||
CHECK(world:get(e, A) == 1)
|
CHECK(world:get(e, A) == 1)
|
||||||
CHECK(world:get(e, B) == 2)
|
CHECK(world:get(e, B) == 2)
|
||||||
CHECK(world:get(e, C) == 3)
|
CHECK(world:get(e, C) == 3)
|
||||||
|
@ -51,7 +230,10 @@ TEST("bulk", function()
|
||||||
CHECK(world:get(e, E) == nil and world:has(e, E))
|
CHECK(world:get(e, E) == nil and world:has(e, E))
|
||||||
CHECK(world:get(e, F) == 5)
|
CHECK(world:get(e, F) == 5)
|
||||||
|
|
||||||
jecs.bulk_insert(world, e, { A, D, E, F, C }, { 10, 40, nil, 50, 30 })
|
jecs.bulk_insert(world, e,
|
||||||
|
{ A, D, E, F, C },
|
||||||
|
{ 10, 40, nil, 50, 30 }
|
||||||
|
)
|
||||||
|
|
||||||
CHECK(world:get(e, A) == 10)
|
CHECK(world:get(e, A) == 10)
|
||||||
CHECK(world:get(e, B) == 2)
|
CHECK(world:get(e, B) == 2)
|
||||||
|
@ -137,6 +319,66 @@ TEST("repro", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:add()", function()
|
TEST("world:add()", function()
|
||||||
|
do CASE "exclusive relations"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
world:add(A, jecs.Exclusive)
|
||||||
|
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "exclusive relations invoke hooks"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
|
||||||
|
local e_ptr: jecs.Entity = (jecs.Rest :: any) + 1
|
||||||
|
|
||||||
|
world:add(A, jecs.Exclusive)
|
||||||
|
local on_remove_call = false
|
||||||
|
world:set(A, jecs.OnRemove, function(e, id)
|
||||||
|
on_remove_call = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
local on_add_call_count = 0
|
||||||
|
world:set(A, jecs.OnAdd, function(e, id)
|
||||||
|
on_add_call_count += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
CHECK(e == e_ptr)
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
CHECK(on_add_call_count == 1)
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
CHECK(on_add_call_count == 2)
|
||||||
|
CHECK(on_remove_call)
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
|
||||||
|
-- We have to ensure that it actually invokes hooks everytime it
|
||||||
|
-- traverses the archetype
|
||||||
|
e = world:entity()
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
CHECK(on_add_call_count == 3)
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
CHECK(on_add_call_count == 4)
|
||||||
|
CHECK(on_remove_call)
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "idempotent"
|
do CASE "idempotent"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local d = dwi(world)
|
local d = dwi(world)
|
||||||
|
@ -188,6 +430,9 @@ TEST("world:children()", function()
|
||||||
local e3 = world:entity()
|
local e3 = world:entity()
|
||||||
world:add(e3, pair(ChildOf, e1))
|
world:add(e3, pair(ChildOf, e1))
|
||||||
|
|
||||||
|
CHECK(world:has(e2, pair(ChildOf, e1)))
|
||||||
|
CHECK(world:has(e3, pair(ChildOf, e1)))
|
||||||
|
|
||||||
local count = 0
|
local count = 0
|
||||||
for entity in world:children(e1) do
|
for entity in world:children(e1) do
|
||||||
count += 1
|
count += 1
|
||||||
|
@ -376,8 +621,51 @@ TEST("world:contains()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:delete()", function()
|
TEST("world:delete()", function()
|
||||||
|
do CASE "pair(OnDelete, Delete)"
|
||||||
|
local world = jecs.world()
|
||||||
|
local ct = world:component()
|
||||||
|
world:add(ct, jecs.pair(jecs.OnDelete, jecs.Delete))
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
|
||||||
|
local dummy = world:entity()
|
||||||
|
|
||||||
|
world:add(e1, ct)
|
||||||
|
world:add(e2, jecs.pair(ct, dummy))
|
||||||
|
|
||||||
|
-- world:delete(dummy)
|
||||||
|
|
||||||
|
-- CHECK(world:contains(e2))
|
||||||
|
|
||||||
|
world:delete(ct)
|
||||||
|
|
||||||
|
CHECK(not world:contains(e1))
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "pair(OnDeleteTarget, Delete)"
|
||||||
|
local world = jecs.world()
|
||||||
|
local ct = world:component()
|
||||||
|
world:add(ct, jecs.pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
|
||||||
|
local dummy = world:entity()
|
||||||
|
|
||||||
|
world:add(e1, ct)
|
||||||
|
|
||||||
|
world:add(e2, jecs.pair(ct, dummy))
|
||||||
|
|
||||||
|
world:delete(dummy)
|
||||||
|
|
||||||
|
CHECK(not world:contains(e2))
|
||||||
|
|
||||||
|
world:delete(ct)
|
||||||
|
|
||||||
|
CHECK(world:contains(e1))
|
||||||
|
end
|
||||||
do CASE "remove (*, R) pairs when relationship is invalidated"
|
do CASE "remove (*, R) pairs when relationship is invalidated"
|
||||||
print("-------")
|
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local e1 = world:entity()
|
local e1 = world:entity()
|
||||||
local e2 = world:entity()
|
local e2 = world:entity()
|
||||||
|
@ -441,7 +729,7 @@ TEST("world:delete()", function()
|
||||||
local A = world:entity()
|
local A = world:entity()
|
||||||
local B = world:entity()
|
local B = world:entity()
|
||||||
|
|
||||||
world:add(Relation, pair(jecs.OnDelete, jecs.Delete))
|
world:add(Relation, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
|
||||||
local entity = world:entity()
|
local entity = world:entity()
|
||||||
|
|
||||||
|
@ -548,7 +836,6 @@ TEST("world:delete()", function()
|
||||||
CHECK(not world:has(id1, Health))
|
CHECK(not world:has(id1, Health))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
do CASE "delete children"
|
do CASE "delete children"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
|
||||||
|
@ -735,6 +1022,16 @@ TEST("world:delete()", function()
|
||||||
CHECK(not world:contains(bob))
|
CHECK(not world:contains(bob))
|
||||||
CHECK(not world:contains(alice))
|
CHECK(not world:contains(alice))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "deleted entity should not be able to be operated on"
|
||||||
|
local world = jecs.world()
|
||||||
|
local e = world:entity()
|
||||||
|
local A = world:component()
|
||||||
|
world:set(e, A, true)
|
||||||
|
world:delete(e)
|
||||||
|
world:set(e, A, true)
|
||||||
|
CHECK(world:has(e, A) == false)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:each()", function()
|
TEST("world:each()", function()
|
||||||
|
@ -765,19 +1062,180 @@ 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 work even if set after the pair has been used"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
world:set(world:entity(), A, 2)
|
||||||
|
world:set(world:entity(), pair(A, B), 2)
|
||||||
|
|
||||||
|
world:added(A, function()
|
||||||
|
ran = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
local entity = world:entity()
|
||||||
|
world:set(entity, pair(A, B), 3)
|
||||||
|
CHECK(ran)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should allow setting signal after Relation has been used as a component"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
world:add(world:entity(), A)
|
||||||
|
|
||||||
|
world:added(A, function()
|
||||||
|
ran = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:add(world:entity(), pair(A, B))
|
||||||
|
CHECK(ran)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should invoke signal for the Relation being set as a key despite a pair with Relation having been cached"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
world:add(world:entity(), pair(A, B))
|
||||||
|
|
||||||
|
world:added(A, function()
|
||||||
|
ran = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:add(world:entity(), A)
|
||||||
|
|
||||||
|
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 "under range start"
|
|
||||||
|
do CASE "spawn entity under min range"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
world:range(400, 1000)
|
world:range(400, 1000)
|
||||||
local id = world:entity() :: number
|
CHECK(world.entity_index.alive_count == 399)
|
||||||
local e = world:entity(id + 5)
|
local e = world:entity(300)
|
||||||
CHECK(e == id + 5)
|
CHECK(world.entity_index.alive_count == 400)
|
||||||
|
local e1 = world:entity(300)
|
||||||
|
CHECK(world.entity_index.alive_count == 400)
|
||||||
|
CHECK(e)
|
||||||
|
end
|
||||||
|
do CASE "axen"
|
||||||
|
local base = jecs.world()
|
||||||
|
base:range(1_000, 2_000)
|
||||||
|
|
||||||
|
local mirror = jecs.world()
|
||||||
|
mirror:range(3_000, 4_000)
|
||||||
|
|
||||||
|
mirror:entity() -- NOTE: this fixes the "attempt to index nil with 'dense'" error
|
||||||
|
|
||||||
|
local foo = base:entity()
|
||||||
|
local bar = mirror:entity(foo)
|
||||||
|
|
||||||
|
base:delete(base:entity()) -- Removing this line stops the error below from happening
|
||||||
|
|
||||||
|
local meow = base:entity()
|
||||||
|
mirror:delete(bar)
|
||||||
|
|
||||||
|
CHECK(jecs.ECS_ID(foo))
|
||||||
|
CHECK(jecs.ECS_ID(meow))
|
||||||
|
local mrrp = mirror:entity(meow) -- jecs, Line 785 - Entity ID is already in use with a different generation
|
||||||
|
CHECK(mrrp == meow)
|
||||||
|
end
|
||||||
|
do CASE "axen2"
|
||||||
|
local world = jecs.world()
|
||||||
|
local mirror = jecs.world()
|
||||||
|
|
||||||
|
world:range(1000, 2000)
|
||||||
|
mirror:range(3000, 4000)
|
||||||
|
|
||||||
|
local foo = world:entity() -- 1000
|
||||||
|
local foo_mirror = mirror:entity(foo) -- 1000
|
||||||
|
CHECK(foo == foo_mirror)
|
||||||
|
|
||||||
|
for index = 1, 5 do
|
||||||
|
world:entity()
|
||||||
|
end
|
||||||
|
|
||||||
|
world:delete(foo)
|
||||||
|
mirror:delete(foo_mirror)
|
||||||
|
|
||||||
|
local bar = world:entity()
|
||||||
|
local bar_mirror = mirror:entity(bar)
|
||||||
|
CHECK(bar == bar_mirror)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "delete outside partitioned range"
|
||||||
|
local server = jecs.world()
|
||||||
|
local client = jecs.world()
|
||||||
|
|
||||||
|
server:range(0, 1000)
|
||||||
|
client:range(1000, 5000)
|
||||||
|
|
||||||
|
local e1 = server:entity()
|
||||||
|
CHECK((e1::any)< 1000)
|
||||||
|
server:delete(e1)
|
||||||
|
local e2 = client:entity(e1)
|
||||||
|
CHECK(e2 == e1)
|
||||||
|
local A = client:component()
|
||||||
|
client:set(e2, A, true)
|
||||||
|
CHECK(client:get(e2, A))
|
||||||
|
|
||||||
|
client:delete(e2)
|
||||||
|
local e3 = client:entity()
|
||||||
|
CHECK(ECS_ID(e3) == 1000)
|
||||||
|
|
||||||
|
local e1v1 = server:entity()
|
||||||
|
local e4 = client:entity(e1v1)
|
||||||
|
CHECK(ECS_ID(e4) == e1::any)
|
||||||
|
CHECK(ECS_GENERATION(e4) == 1)
|
||||||
|
CHECK(not client:contains(e2))
|
||||||
|
CHECK(client:contains(e4))
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "under range start"
|
||||||
|
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(400, 1000)
|
||||||
|
local id = world:entity()
|
||||||
|
local e = world:entity(id::any + 5)
|
||||||
|
CHECK(e::any == (id::any) + 5)
|
||||||
|
|
||||||
CHECK(world:contains(e))
|
CHECK(world:contains(e))
|
||||||
local e2 = world:entity(399)
|
local e2 = world:entity(399)
|
||||||
CHECK(world:contains(e2))
|
CHECK(world:contains(e2))
|
||||||
world:delete(e2)
|
world:delete(e2)
|
||||||
CHECK(not world:contains(e2))
|
CHECK(not world:contains(e2))
|
||||||
local e2v1 = world:entity(399) :: number
|
local e2v1 = world:entity(399)
|
||||||
CHECK(world:contains(e2v1))
|
CHECK(world:contains(e2v1))
|
||||||
CHECK(ECS_ID(e2v1) == 399)
|
CHECK(ECS_ID(e2v1) == 399)
|
||||||
CHECK(ECS_GENERATION(e2v1) == 0)
|
CHECK(ECS_GENERATION(e2v1) == 0)
|
||||||
|
@ -790,13 +1248,13 @@ TEST("world:range()", function()
|
||||||
CHECK(world:contains(e2))
|
CHECK(world:contains(e2))
|
||||||
world:delete(e2)
|
world:delete(e2)
|
||||||
CHECK(not world:contains(e2))
|
CHECK(not world:contains(e2))
|
||||||
local e2v1 = world:entity(405) :: number
|
local e2v1 = world:entity(405)
|
||||||
CHECK(world:contains(e2v1))
|
CHECK(world:contains(e2v1))
|
||||||
CHECK(ECS_ID(e2v1) == 405)
|
CHECK(ECS_ID(e2v1) == 405)
|
||||||
CHECK(ECS_GENERATION(e2v1) == 0)
|
CHECK(ECS_GENERATION(e2v1) == 0)
|
||||||
|
|
||||||
world:delete(e2v1)
|
world:delete(e2v1)
|
||||||
local e2v2 = world:entity(e2v1) :: number
|
local e2v2 = world:entity(e2v1)
|
||||||
CHECK(ECS_ID(e2v2) == 405)
|
CHECK(ECS_ID(e2v2) == 405)
|
||||||
CHECK(ECS_GENERATION(e2v2) == 0)
|
CHECK(ECS_GENERATION(e2v2) == 0)
|
||||||
end
|
end
|
||||||
|
@ -805,9 +1263,10 @@ end)
|
||||||
TEST("world:entity()", function()
|
TEST("world:entity()", function()
|
||||||
do CASE "desired id"
|
do CASE "desired id"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local id = world:entity() :: number
|
local id = world:entity()
|
||||||
local e = world:entity(id + 5)
|
local offset: jecs.Entity = (id ::any) + 5
|
||||||
CHECK(e == id + 5)
|
local e = world:entity(offset)
|
||||||
|
CHECK(e == offset)
|
||||||
CHECK(world:contains(e))
|
CHECK(world:contains(e))
|
||||||
local e2 = world:entity(399)
|
local e2 = world:entity(399)
|
||||||
CHECK(world:contains(e2))
|
CHECK(world:contains(e2))
|
||||||
|
@ -825,7 +1284,7 @@ TEST("world:entity()", function()
|
||||||
end
|
end
|
||||||
do CASE "generations"
|
do CASE "generations"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local e = world:entity() :: any
|
local e = world:entity()
|
||||||
CHECK(ECS_ID(e) == 1 + jecs.Rest :: any)
|
CHECK(ECS_ID(e) == 1 + jecs.Rest :: any)
|
||||||
CHECK(ECS_GENERATION(e) == 0) -- 0
|
CHECK(ECS_GENERATION(e) == 0) -- 0
|
||||||
e = ECS_GENERATION_INC(e)
|
e = ECS_GENERATION_INC(e)
|
||||||
|
@ -875,11 +1334,11 @@ TEST("world:entity()", function()
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:delete(e)
|
world:delete(e)
|
||||||
end
|
end
|
||||||
local e = world:entity() :: number
|
local e = world:entity()
|
||||||
CHECK(ECS_ID(e) == pin)
|
CHECK(ECS_ID(e) == pin)
|
||||||
CHECK(ECS_GENERATION(e) == 2^16-1)
|
CHECK(ECS_GENERATION(e) == 2^16-1)
|
||||||
world:delete(e)
|
world:delete(e)
|
||||||
e = world:entity() :: number
|
e = world:entity()
|
||||||
CHECK(ECS_ID(e) == pin)
|
CHECK(ECS_ID(e) == pin)
|
||||||
CHECK(ECS_GENERATION(e) == 0)
|
CHECK(ECS_GENERATION(e) == 0)
|
||||||
end
|
end
|
||||||
|
@ -916,6 +1375,7 @@ end)
|
||||||
|
|
||||||
TEST("world:query()", function()
|
TEST("world:query()", function()
|
||||||
local N = 2^8
|
local N = 2^8
|
||||||
|
|
||||||
do CASE "cached"
|
do CASE "cached"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local Foo = world:component()
|
local Foo = world:component()
|
||||||
|
@ -952,6 +1412,26 @@ TEST("world:query()", function()
|
||||||
world:delete(Foo)
|
world:delete(Foo)
|
||||||
CHECK(#q:archetypes() == 0)
|
CHECK(#q:archetypes() == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "3 components"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component() :: jecs.Entity<boolean>
|
||||||
|
local B = world:component() :: jecs.Entity<boolean>
|
||||||
|
local C = world:component() :: jecs.Entity<boolean>
|
||||||
|
local e = world:entity()
|
||||||
|
world:set(e, A, true)
|
||||||
|
world:set(e, B, true)
|
||||||
|
world:set(e, C, true)
|
||||||
|
local q = world:query(A, B, C):cached()
|
||||||
|
local counter = 0
|
||||||
|
for x, a, b, c in q:iter() do
|
||||||
|
counter += 1
|
||||||
|
CHECK(a)
|
||||||
|
CHECK(b)
|
||||||
|
CHECK(c)
|
||||||
|
end
|
||||||
|
CHECK(counter == 1)
|
||||||
|
end
|
||||||
do CASE "multiple iter"
|
do CASE "multiple iter"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local A = world:component() :: jecs.Entity<string>
|
local A = world:component() :: jecs.Entity<string>
|
||||||
|
@ -1133,6 +1613,7 @@ TEST("world:query()", function()
|
||||||
|
|
||||||
for i = 1, 9 do
|
for i = 1, 9 do
|
||||||
local id = world:component()
|
local id = world:component()
|
||||||
|
world:component() -- make the components sparsely interleaved
|
||||||
components[i] = id
|
components[i] = id
|
||||||
end
|
end
|
||||||
local e1 = world:entity()
|
local e1 = world:entity()
|
||||||
|
@ -1376,7 +1857,7 @@ TEST("world:query()", function()
|
||||||
world:add(e2, B)
|
world:add(e2, B)
|
||||||
|
|
||||||
local count = 0
|
local count = 0
|
||||||
for id in world:query(A) :: any do
|
for id in world:query(A) do
|
||||||
world:clear(id)
|
world:clear(id)
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
@ -1621,7 +2102,9 @@ end)
|
||||||
TEST("#repro2", function()
|
TEST("#repro2", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local Lifetime = world:component() :: Id<number>
|
local Lifetime = world:component() :: Id<number>
|
||||||
|
world:set(Lifetime, jecs.Name, "Lifetime")
|
||||||
local Particle = world:entity()
|
local Particle = world:entity()
|
||||||
|
world:set(Particle, jecs.Name, "Particle")
|
||||||
local Beam = world:entity()
|
local Beam = world:entity()
|
||||||
|
|
||||||
local entity = world:entity()
|
local entity = world:entity()
|
||||||
|
@ -1629,19 +2112,24 @@ TEST("#repro2", function()
|
||||||
world:set(entity, pair(Lifetime, Beam), 2)
|
world:set(entity, pair(Lifetime, Beam), 2)
|
||||||
world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise
|
world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise
|
||||||
|
|
||||||
|
CHECK(world:get(entity, pair(Lifetime, Particle)) == 1)
|
||||||
|
CHECK(world:get(entity, pair(Lifetime, Beam)) == 2)
|
||||||
|
|
||||||
|
CHECK(world:target(entity, Lifetime, 0) == Particle)
|
||||||
|
CHECK(world:target(entity, Lifetime, 1) == Beam)
|
||||||
|
|
||||||
-- entity_visualizer.components(world, entity)
|
-- entity_visualizer.components(world, entity)
|
||||||
|
|
||||||
|
-- print(CHECK(world:has(jecs.ChildOf, jecs.Exclusive)))
|
||||||
|
|
||||||
for e in world:each(pair(Lifetime, __)) do
|
for e in world:each(pair(Lifetime, __)) do
|
||||||
local i = 0
|
local i = 0
|
||||||
local nth = world:target(e, Lifetime, i)
|
local nth = world:target(e, Lifetime, i)
|
||||||
while nth do
|
while nth do
|
||||||
-- entity_visualizer.components(world, e)
|
-- entity_visualizer.components(world, e)
|
||||||
|
|
||||||
local data = world:get(e, pair(Lifetime, nth)) :: number
|
local data = world:get(e, pair(Lifetime, nth)) :: number
|
||||||
data -= 1
|
if data > 0 then
|
||||||
if data <= 0 then
|
data -= 1
|
||||||
world:remove(e, pair(Lifetime, nth))
|
|
||||||
else
|
|
||||||
world:set(e, pair(Lifetime, nth), data)
|
world:set(e, pair(Lifetime, nth), data)
|
||||||
end
|
end
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -1649,7 +2137,7 @@ TEST("#repro2", function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
CHECK(not world:has(entity, pair(Lifetime, Particle)))
|
CHECK(world:get(entity, pair(Lifetime, Particle)) == 0)
|
||||||
CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
|
CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -1809,14 +2297,14 @@ TEST("change tracking", function()
|
||||||
world:set(e2, Foo, 2)
|
world:set(e2, Foo, 2)
|
||||||
|
|
||||||
local i = 0
|
local i = 0
|
||||||
for e, new in q1 :: any do
|
for e, new in q1 do
|
||||||
i += 1
|
i += 1
|
||||||
world:set(e, pair(Previous, Foo), new)
|
world:set(e, pair(Previous, Foo), new)
|
||||||
end
|
end
|
||||||
|
|
||||||
CHECK(i == 2)
|
CHECK(i == 2)
|
||||||
local j = 0
|
local j = 0
|
||||||
for e, new in q1 :: any do
|
for e, new in q1 do
|
||||||
j += 1
|
j += 1
|
||||||
world:set(e, pair(Previous, Foo), new)
|
world:set(e, pair(Previous, Foo), new)
|
||||||
end
|
end
|
||||||
|
@ -1837,14 +2325,14 @@ TEST("change tracking", function()
|
||||||
world:set(testEntity, component, 10)
|
world:set(testEntity, component, 10)
|
||||||
|
|
||||||
local i = 0
|
local i = 0
|
||||||
for entity, number in q1 :: any do
|
for entity, number in q1 do
|
||||||
i += 1
|
i += 1
|
||||||
world:add(testEntity, tag)
|
world:add(testEntity, tag)
|
||||||
end
|
end
|
||||||
|
|
||||||
CHECK(i == 1)
|
CHECK(i == 1)
|
||||||
|
|
||||||
for e, n in q1 :: any do
|
for e, n in q1 do
|
||||||
world:set(e, pair(previous, component), n)
|
world:set(e, pair(previous, component), n)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.7.2"
|
version = "0.9.0-rc.8"
|
||||||
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