mirror of
https://github.com/Ukendio/jecs.git
synced 2025-08-04 19:29:18 +00:00
Compare commits
No commits in common. "ebc39c8b28082f4290c6077d8475079bc8314582" and "210d62d4636c86d90cbceea2c0c5c00e78c9936f" have entirely different histories.
ebc39c8b28
...
210d62d463
5 changed files with 167 additions and 159 deletions
3
jecs.d.ts
vendored
3
jecs.d.ts
vendored
|
@ -321,6 +321,3 @@ export type 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
|
281
jecs.luau
281
jecs.luau
|
@ -19,7 +19,8 @@ export type Archetype = {
|
|||
type: string,
|
||||
entities: { Entity },
|
||||
columns: { Column },
|
||||
columns_map: { [Id]: Column }
|
||||
columns_map: { [Id]: Column },
|
||||
dead: boolean,
|
||||
}
|
||||
|
||||
export type QueryInner = {
|
||||
|
@ -860,7 +861,8 @@ local function archetype_create(world: World, id_types: { Id }, ty, prev: i53?):
|
|||
entities = {},
|
||||
id = archetype_id,
|
||||
type = ty,
|
||||
types = id_types
|
||||
types = id_types,
|
||||
dead = false,
|
||||
}
|
||||
|
||||
archetype_register(world, archetype, false)
|
||||
|
@ -899,6 +901,10 @@ local function archetype_ensure(world: World, id_types: { Id }): Archetype
|
|||
local ty = hash(id_types)
|
||||
local archetype = world.archetype_index[ty]
|
||||
if archetype then
|
||||
if archetype.dead then
|
||||
archetype_register(world, archetype)
|
||||
archetype.dead = false :: any
|
||||
end
|
||||
return archetype
|
||||
end
|
||||
|
||||
|
@ -1076,8 +1082,8 @@ local function archetype_destroy(world: World, archetype: Archetype)
|
|||
end
|
||||
|
||||
local archetype_id = archetype.id
|
||||
world.archetypes[archetype_id] = nil :: any
|
||||
world.archetype_index[archetype.type] = nil :: any
|
||||
-- world.archetypes[archetype_id] = nil :: any
|
||||
-- world.archetype_index[archetype.type] = nil :: any
|
||||
local columns_map = archetype.columns_map
|
||||
|
||||
for id in columns_map do
|
||||
|
@ -1098,6 +1104,8 @@ local function archetype_destroy(world: World, archetype: Archetype)
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
archetype.dead = true
|
||||
end
|
||||
|
||||
local function NOOP() end
|
||||
|
@ -2025,7 +2033,7 @@ local function world_children<a>(world: World, parent: Id<a>)
|
|||
return world_each(world, ECS_PAIR(EcsChildOf, parent::number))
|
||||
end
|
||||
|
||||
local function ecs_bulk_insert(world: World, entity: Entity, ids: { Id }, values: { any })
|
||||
local function ecs_bulk_insert(world: World, entity: Entity, ids: { Entity }, values: { any })
|
||||
local entity_index = world.entity_index
|
||||
local r = entity_index_try_get(entity_index, entity)
|
||||
if not r then
|
||||
|
@ -2103,7 +2111,7 @@ local function ecs_bulk_insert(world: World, entity: Entity, ids: { Id }, values
|
|||
end
|
||||
end
|
||||
|
||||
local function ecs_bulk_remove(world: World, entity: Entity, ids: { Id })
|
||||
local function ecs_bulk_remove(world: World, entity: Entity, ids: { Entity })
|
||||
local entity_index = world.entity_index
|
||||
local r = entity_index_try_get(entity_index, entity)
|
||||
if not r then
|
||||
|
@ -2115,7 +2123,7 @@ local function ecs_bulk_remove(world: World, entity: Entity, ids: { Id })
|
|||
return
|
||||
end
|
||||
|
||||
local remove: { [Id]: boolean } = {}
|
||||
local remove: { [Entity]: boolean } = {}
|
||||
|
||||
local columns_map = from.columns_map
|
||||
|
||||
|
@ -2154,12 +2162,14 @@ end
|
|||
local function world_new()
|
||||
local eindex_dense_array = {} :: { Entity }
|
||||
local eindex_sparse_array = {} :: { Record }
|
||||
local eindex_alive_count = 0
|
||||
local eindex_max_id = 0
|
||||
|
||||
local entity_index = {
|
||||
dense_array = eindex_dense_array,
|
||||
sparse_array = eindex_sparse_array,
|
||||
alive_count = 0,
|
||||
max_id = 0,
|
||||
alive_count = eindex_alive_count,
|
||||
max_id = eindex_max_id,
|
||||
} :: EntityIndex
|
||||
|
||||
local component_index = {} :: ComponentIndex
|
||||
|
@ -2297,92 +2307,6 @@ local function world_new()
|
|||
return r
|
||||
end
|
||||
|
||||
local function inner_world_set<T, a>(world: World, entity: Entity<T>, id: Id<a>, data: a): ()
|
||||
local record = inner_entity_index_try_get_unsafe(entity :: number)
|
||||
if not record then
|
||||
return
|
||||
end
|
||||
|
||||
local from: Archetype = record.archetype
|
||||
local src = from or ROOT_ARCHETYPE
|
||||
local column = src.columns_map[id]
|
||||
if column then
|
||||
local idr = component_index[id]
|
||||
column[record.row] = data
|
||||
|
||||
-- If the archetypes are the same it can avoid moving the entity
|
||||
-- and just set the data directly.
|
||||
local on_change = idr.on_change
|
||||
if on_change then
|
||||
on_change(entity, id, data)
|
||||
end
|
||||
else
|
||||
local to: Archetype
|
||||
local idr: ComponentRecord
|
||||
if ECS_IS_PAIR(id::number) then
|
||||
local edge = archetype_edges[src.id]
|
||||
to = edge[id]
|
||||
if not to then
|
||||
local first = ECS_PAIR_FIRST(id::number)
|
||||
local wc = ECS_PAIR(first, EcsWildcard)
|
||||
idr = component_index[wc]
|
||||
if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
|
||||
local cr = idr.records[src.id]
|
||||
if cr then
|
||||
local on_remove = idr.on_remove
|
||||
local id_types = src.types
|
||||
if on_remove then
|
||||
on_remove(entity, id_types[cr])
|
||||
src = record.archetype
|
||||
id_types = src.types
|
||||
cr = idr.records[src.id]
|
||||
end
|
||||
local dst = table.clone(id_types)
|
||||
dst[cr] = id
|
||||
to = archetype_ensure(world, dst)
|
||||
else
|
||||
to = find_archetype_with(world, id, src)
|
||||
idr = component_index[id]
|
||||
end
|
||||
else
|
||||
to = find_archetype_with(world, id, src)
|
||||
idr = component_index[id]
|
||||
end
|
||||
edge[id] = to
|
||||
archetype_edges[to.id][id] = src
|
||||
else
|
||||
idr = component_index[id]
|
||||
end
|
||||
else
|
||||
local edges = archetype_edges
|
||||
local edge = edges[src.id]
|
||||
|
||||
to = edge[id] :: Archetype
|
||||
if not to then
|
||||
to = find_archetype_with(world, id, src)
|
||||
edge[id] = to
|
||||
edges[to.id][id] = src
|
||||
end
|
||||
idr = component_index[id]
|
||||
end
|
||||
|
||||
if from then
|
||||
-- If there was a previous archetype, then the entity needs to move the archetype
|
||||
inner_entity_move(entity_index, entity, record, to)
|
||||
else
|
||||
new_entity(entity, record, to)
|
||||
end
|
||||
|
||||
column = to.columns_map[id]
|
||||
column[record.row] = data
|
||||
|
||||
local on_add = idr.on_add
|
||||
if on_add then
|
||||
on_add(entity, id, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function inner_world_add<T, a>(
|
||||
world: World,
|
||||
entity: Entity<T>,
|
||||
|
@ -2395,15 +2319,11 @@ local function world_new()
|
|||
end
|
||||
|
||||
local from = record.archetype
|
||||
local src = from or ROOT_ARCHETYPE
|
||||
if src.columns_map[id] then
|
||||
return
|
||||
end
|
||||
local to: Archetype
|
||||
local idr: ComponentRecord
|
||||
if ECS_IS_PAIR(id::number) then
|
||||
local src = from or ROOT_ARCHETYPE
|
||||
local edge = archetype_edges[src.id]
|
||||
to = edge[id]
|
||||
local to = edge[id]
|
||||
local idr: ComponentRecord
|
||||
if not to then
|
||||
local first = ECS_PAIR_FIRST(id::number)
|
||||
local wc = ECS_PAIR(first, EcsWildcard)
|
||||
|
@ -2431,23 +2351,18 @@ local function world_new()
|
|||
idr = component_index[id]
|
||||
end
|
||||
edge[id] = to
|
||||
archetype_edges[to.id][id] = src
|
||||
else
|
||||
idr = component_index[id]
|
||||
end
|
||||
else
|
||||
local edges = archetype_edges
|
||||
local edge = edges[src.id]
|
||||
|
||||
to = edge[id] :: Archetype
|
||||
if not to then
|
||||
to = find_archetype_with(world, id, src)
|
||||
if to.dead then
|
||||
archetype_register(world, to, true)
|
||||
edge[id] = to
|
||||
edges[to.id][id] = src
|
||||
archetype_edges[to.id][id] = src
|
||||
to.dead = false
|
||||
end
|
||||
idr = component_index[id]
|
||||
end
|
||||
|
||||
if from == to then
|
||||
return
|
||||
end
|
||||
if from then
|
||||
inner_entity_move(entity_index, entity, record, to)
|
||||
else
|
||||
|
@ -2461,6 +2376,27 @@ local function world_new()
|
|||
if on_add then
|
||||
on_add(entity, id)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
local to = archetype_traverse_add(world, id, from)
|
||||
if from == to then
|
||||
return
|
||||
end
|
||||
if from then
|
||||
inner_entity_move(entity_index, entity, record, to)
|
||||
else
|
||||
if #to.types > 0 then
|
||||
new_entity(entity, record, to)
|
||||
end
|
||||
end
|
||||
|
||||
local idr = component_index[id]
|
||||
local on_add = idr.on_add
|
||||
|
||||
if on_add then
|
||||
on_add(entity, id)
|
||||
end
|
||||
end
|
||||
|
||||
local function inner_world_get(world: World, entity: Entity,
|
||||
|
@ -2559,6 +2495,102 @@ local function world_new()
|
|||
return inner_world_target(world, entity, EcsChildOf, 0)
|
||||
end
|
||||
|
||||
local function inner_archetype_traverse_add(id: Id, from: Archetype): Archetype
|
||||
from = from or ROOT_ARCHETYPE
|
||||
if from.columns_map[id] then
|
||||
return from
|
||||
end
|
||||
local edges = archetype_edges
|
||||
local edge = edges[from.id]
|
||||
|
||||
local to = edge[id] :: Archetype
|
||||
if not to then
|
||||
to = find_archetype_with(world, id, from)
|
||||
edge[id] = to
|
||||
edges[to.id][id] = from
|
||||
end
|
||||
|
||||
return to
|
||||
end
|
||||
|
||||
local function inner_world_set<T, a>(world: World, entity: Entity<T>, id: Id<a>, data: a): ()
|
||||
local record = inner_entity_index_try_get_unsafe(entity :: number)
|
||||
if not record then
|
||||
return
|
||||
end
|
||||
|
||||
local from: Archetype = record.archetype
|
||||
local src = from or ROOT_ARCHETYPE
|
||||
local column = src.columns_map[id]
|
||||
if column then
|
||||
local idr = component_index[id]
|
||||
column[record.row] = data
|
||||
|
||||
-- If the archetypes are the same it can avoid moving the entity
|
||||
-- and just set the data directly.
|
||||
local on_change = idr.on_change
|
||||
if on_change then
|
||||
on_change(entity, id, data)
|
||||
end
|
||||
else
|
||||
local to: Archetype
|
||||
local idr: ComponentRecord
|
||||
if ECS_IS_PAIR(id::number) then
|
||||
local edge = archetype_edges[src.id]
|
||||
to = edge[id]
|
||||
if not to then
|
||||
local first = ECS_PAIR_FIRST(id::number)
|
||||
local wc = ECS_PAIR(first, EcsWildcard)
|
||||
idr = component_index[wc]
|
||||
if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
|
||||
local cr = idr.records[src.id]
|
||||
if cr then
|
||||
local on_remove = idr.on_remove
|
||||
local id_types = src.types
|
||||
if on_remove then
|
||||
on_remove(entity, id_types[cr])
|
||||
src = record.archetype
|
||||
id_types = src.types
|
||||
cr = idr.records[src.id]
|
||||
end
|
||||
local dst = table.clone(id_types)
|
||||
dst[cr] = id
|
||||
to = archetype_ensure(world, dst)
|
||||
else
|
||||
to = find_archetype_with(world, id, src)
|
||||
idr = component_index[id]
|
||||
end
|
||||
else
|
||||
to = find_archetype_with(world, id, src)
|
||||
idr = component_index[id]
|
||||
end
|
||||
edge[id] = to
|
||||
archetype_edges[to.id][id] = src
|
||||
else
|
||||
idr = component_index[id]
|
||||
end
|
||||
else
|
||||
to = inner_archetype_traverse_add(id, from)
|
||||
idr = component_index[id]
|
||||
end
|
||||
|
||||
if from then
|
||||
-- If there was a previous archetype, then the entity needs to move the archetype
|
||||
inner_entity_move(entity_index, entity, record, to)
|
||||
else
|
||||
new_entity(entity, record, to)
|
||||
end
|
||||
|
||||
column = to.columns_map[id]
|
||||
column[record.row] = data
|
||||
|
||||
local on_add = idr.on_add
|
||||
if on_add then
|
||||
on_add(entity, id, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function inner_world_entity<T>(world: World, entity: Entity<T>?): Entity<T>
|
||||
if entity then
|
||||
local index = ECS_ID(entity :: number)
|
||||
|
@ -2570,6 +2602,8 @@ local function world_new()
|
|||
if not dense or r.dense == 0 then
|
||||
r.dense = index
|
||||
dense = index
|
||||
local any = eindex_dense_array[dense]
|
||||
if any == entity then
|
||||
local e_swap = eindex_dense_array[dense]
|
||||
local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record
|
||||
|
||||
|
@ -2580,6 +2614,7 @@ local function world_new()
|
|||
|
||||
eindex_dense_array[dense] = e_swap
|
||||
eindex_dense_array[alive_count] = entity
|
||||
end
|
||||
return entity
|
||||
end
|
||||
|
||||
|
@ -2601,7 +2636,7 @@ local function world_new()
|
|||
|
||||
return entity
|
||||
else
|
||||
for i = entity_index.max_id + 1, index do
|
||||
for i = eindex_max_id + 1, index do
|
||||
eindex_sparse_array[i] = { dense = i } :: Record
|
||||
eindex_dense_array[i] = i
|
||||
end
|
||||
|
@ -3086,13 +3121,6 @@ return {
|
|||
|
||||
pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
|
||||
|
||||
IS_PAIR = (ECS_IS_PAIR :: any) :: <P, O>(pair: Pair<P, O>) -> boolean,
|
||||
ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Pair<P, O>) -> Id<P>,
|
||||
ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Pair<P, O>) -> Id<O>,
|
||||
pair_first = (ecs_pair_first :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<P>,
|
||||
pair_second = (ecs_pair_second :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<O>,
|
||||
entity_index_get_alive = entity_index_get_alive,
|
||||
|
||||
-- Inwards facing API for testing
|
||||
ECS_ID = ECS_ENTITY_T_LO,
|
||||
ECS_GENERATION_INC = ECS_GENERATION_INC,
|
||||
|
@ -3100,8 +3128,13 @@ return {
|
|||
ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
|
||||
ECS_ID_DELETE = ECS_ID_DELETE,
|
||||
ECS_META_RESET = ECS_META_RESET,
|
||||
ECS_COMBINE = ECS_COMBINE,
|
||||
ECS_ENTITY_MASK = ECS_ENTITY_MASK,
|
||||
|
||||
IS_PAIR = (ECS_IS_PAIR :: any) :: <P, O>(pair: Pair<P, O>) -> boolean,
|
||||
ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Pair<P, O>) -> Id<P>,
|
||||
ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Pair<P, O>) -> Id<O>,
|
||||
pair_first = (ecs_pair_first :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<P>,
|
||||
pair_second = (ecs_pair_second :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<O>,
|
||||
entity_index_get_alive = entity_index_get_alive,
|
||||
|
||||
archetype_append_to_records = archetype_append_to_records,
|
||||
id_record_ensure = id_record_ensure,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@rbxts/jecs",
|
||||
"version": "0.9.0-rc.0",
|
||||
"version": "0.8.3",
|
||||
"description": "Stupidly fast Entity Component System",
|
||||
"main": "jecs.luau",
|
||||
"repository": {
|
||||
|
|
|
@ -24,25 +24,6 @@ type Id<T=unknown> = jecs.Id<T>
|
|||
local entity_visualiser = require("@tools/entity_visualiser")
|
||||
local dwi = entity_visualiser.stringify
|
||||
|
||||
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()
|
||||
|
@ -148,7 +129,6 @@ TEST("repeated pairs", function()
|
|||
CHECK(count == 1)
|
||||
CHECK(world:each(p2)() == e2) -- Fails
|
||||
end)
|
||||
|
||||
TEST("repro", function()
|
||||
local world = jecs.world()
|
||||
local data = world:component()
|
||||
|
@ -177,7 +157,6 @@ TEST("repro", function()
|
|||
end
|
||||
CHECK(count == 1)
|
||||
count = 0
|
||||
print("----")
|
||||
world:add(e2v1, jecs.pair(relation, e1v1))
|
||||
CHECK(world:has(e2v1, jecs.pair(relation, e1v1)))
|
||||
|
||||
|
@ -185,7 +164,6 @@ TEST("repro", function()
|
|||
count += 1
|
||||
end
|
||||
|
||||
print(count)
|
||||
CHECK(count==1)
|
||||
end)
|
||||
TEST("bulk", function()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ukendio/jecs"
|
||||
version = "0.9.0-rc.0"
|
||||
version = "0.8.3"
|
||||
registry = "https://github.com/UpliftGames/wally-index"
|
||||
realm = "shared"
|
||||
license = "MIT"
|
||||
|
|
Loading…
Reference in a new issue