mirror of
https://github.com/Ukendio/jecs.git
synced 2025-08-04 19:29:18 +00:00
Compare commits
7 commits
23540e5919
...
ad5ed3b5ea
Author | SHA1 | Date | |
---|---|---|---|
|
ad5ed3b5ea | ||
|
362490d25e | ||
|
6c1793f853 | ||
|
6b6f6fb961 | ||
|
29350e6ec3 | ||
|
eed1b6179e | ||
|
cf94a48a40 |
5 changed files with 180 additions and 51 deletions
|
@ -1,9 +1,10 @@
|
||||||
|
--!strict
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
|
|
||||||
export type PatchedWorld = jecs.World & {
|
export type PatchedWorld = jecs.World & {
|
||||||
added: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (),
|
added: <T>(PatchedWorld, jecs.Id<T>, <e>(e: jecs.Entity<e>, id: jecs.Id<T>, value: T?) -> ()) -> () -> (),
|
||||||
removed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (),
|
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) -> ()) -> () -> (),
|
changed: <T>(PatchedWorld, jecs.Id<T>, <e>(e: jecs.Entity<e>, id: jecs.Id<T>, value: T) -> ()) -> () -> (),
|
||||||
observer: <T...>(
|
observer: <T...>(
|
||||||
PatchedWorld,
|
PatchedWorld,
|
||||||
jecs.Query<T...>,
|
jecs.Query<T...>,
|
||||||
|
@ -16,28 +17,41 @@ export type PatchedWorld = jecs.World & {
|
||||||
) -> ()
|
) -> ()
|
||||||
}
|
}
|
||||||
|
|
||||||
local function observers_new(world, query, callback)
|
local function observers_new(
|
||||||
local terms = query.filter_with :: { jecs.Id }
|
world: PatchedWorld,
|
||||||
if not terms then
|
query: any,
|
||||||
local ids = query.ids
|
callback: (<T, a>(jecs.Entity<T>, jecs.Id<a>, value: a?) -> ())?
|
||||||
query.filter_with = ids
|
)
|
||||||
terms = ids
|
query = query:cached()
|
||||||
|
|
||||||
|
local archetypes = {}
|
||||||
|
local terms = query.ids
|
||||||
|
local first = terms[1]
|
||||||
|
|
||||||
|
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
||||||
|
observers_on_create[#observers_on_create].callback = function(archetype)
|
||||||
|
archetypes[archetype.id] = true
|
||||||
|
end
|
||||||
|
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
|
||||||
|
observers_on_delete[#observers_on_delete].callback = function(archetype)
|
||||||
|
archetypes[archetype.id] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local entity_index = world.entity_index :: any
|
local entity_index = world.entity_index :: any
|
||||||
local i = 0
|
local i = 0
|
||||||
local entities = {}
|
local entities = {}
|
||||||
local function emplaced(entity, id, value)
|
|
||||||
local r = jecs.entity_index_try_get_fast(
|
|
||||||
entity_index, entity :: any)
|
|
||||||
|
|
||||||
if not r then
|
local function emplaced<T, a>(
|
||||||
return
|
entity: jecs.Entity<T>,
|
||||||
end
|
id: jecs.Id<a>,
|
||||||
|
value: a?
|
||||||
|
)
|
||||||
|
local r = jecs.entity_index_try_get_fast(
|
||||||
|
entity_index, entity :: any) :: jecs.Record
|
||||||
|
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
|
||||||
if jecs.query_match(query, archetype) then
|
if archetypes[archetype.id] then
|
||||||
i += 1
|
i += 1
|
||||||
entities[i] = entity
|
entities[i] = entity
|
||||||
if callback ~= nil then
|
if callback ~= nil then
|
||||||
|
@ -110,27 +124,36 @@ local function join(world, component)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function monitors_new(world, query, callback)
|
local function monitors_new(world, query, callback)
|
||||||
local terms = query.filter_with :: { jecs.Id }
|
query = query:cached()
|
||||||
if not terms then
|
|
||||||
local ids = query.ids
|
local archetypes = {}
|
||||||
query.filter_with = ids
|
local terms = query.ids
|
||||||
terms = ids
|
local first = terms[1]
|
||||||
|
|
||||||
|
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
||||||
|
observers_on_create[#observers_on_create].callback = function(archetype)
|
||||||
|
archetypes[archetype.id] = true
|
||||||
|
end
|
||||||
|
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
|
||||||
|
observers_on_delete[#observers_on_delete].callback = function(archetype)
|
||||||
|
archetypes[archetype.id] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local entity_index = world.entity_index :: any
|
local entity_index = world.entity_index :: any
|
||||||
local i = 0
|
local i = 0
|
||||||
local entities = {}
|
local entities = {}
|
||||||
local function emplaced(entity, id, value)
|
|
||||||
local r = jecs.entity_index_try_get_fast(
|
|
||||||
entity_index, entity :: any)
|
|
||||||
|
|
||||||
if not r then
|
local function emplaced<T, a>(
|
||||||
return
|
entity: jecs.Entity<T>,
|
||||||
end
|
id: jecs.Id<a>,
|
||||||
|
value: a?
|
||||||
|
)
|
||||||
|
local r = jecs.entity_index_try_get_fast(
|
||||||
|
entity_index, entity :: any) :: jecs.Record
|
||||||
|
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
|
||||||
if jecs.query_match(query, archetype) then
|
if archetypes[archetype.id] then
|
||||||
i += 1
|
i += 1
|
||||||
entities[i] = entity
|
entities[i] = entity
|
||||||
if callback ~= nil then
|
if callback ~= nil then
|
||||||
|
|
53
jecs.luau
53
jecs.luau
|
@ -2566,30 +2566,42 @@ local function world_new()
|
||||||
if not dense or r.dense == 0 then
|
if not dense or r.dense == 0 then
|
||||||
r.dense = index
|
r.dense = index
|
||||||
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
|
||||||
|
|
||||||
|
r_swap.dense = dense
|
||||||
|
alive_count += 1
|
||||||
|
entity_index.alive_count = alive_count
|
||||||
|
r.dense = alive_count
|
||||||
|
|
||||||
|
eindex_dense_array[dense] = e_swap
|
||||||
|
eindex_dense_array[alive_count] = entity
|
||||||
|
end
|
||||||
|
return entity
|
||||||
end
|
end
|
||||||
|
|
||||||
local any = eindex_dense_array[dense]
|
local any = eindex_dense_array[dense]
|
||||||
if dense <= alive_count then
|
if any ~= entity then
|
||||||
if any ~= entity then
|
if alive_count <= dense then
|
||||||
error("Entity ID is already in use with a different generation")
|
local e_swap = eindex_dense_array[dense]
|
||||||
else
|
local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record
|
||||||
return entity
|
|
||||||
|
r_swap.dense = dense
|
||||||
|
alive_count += 1
|
||||||
|
entity_index.alive_count = alive_count
|
||||||
|
r.dense = alive_count
|
||||||
|
|
||||||
|
eindex_dense_array[dense] = e_swap
|
||||||
|
eindex_dense_array[alive_count] = entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local e_swap = eindex_dense_array[dense]
|
|
||||||
local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record
|
|
||||||
alive_count += 1
|
|
||||||
entity_index.alive_count = alive_count
|
|
||||||
r_swap.dense = dense
|
|
||||||
r.dense = alive_count
|
|
||||||
eindex_dense_array[dense] = e_swap
|
|
||||||
eindex_dense_array[alive_count] = entity
|
|
||||||
|
|
||||||
return entity
|
return entity
|
||||||
else
|
else
|
||||||
for i = eindex_max_id + 1, index do
|
for i = eindex_max_id + 1, index do
|
||||||
eindex_sparse_array[i]= { dense = i } :: Record
|
eindex_sparse_array[i] = { dense = i } :: Record
|
||||||
eindex_dense_array[i] = i
|
eindex_dense_array[i] = i
|
||||||
end
|
end
|
||||||
entity_index.max_id = index
|
entity_index.max_id = index
|
||||||
|
@ -2728,7 +2740,6 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function inner_world_delete<T>(world: World, entity: Entity<T>)
|
local function inner_world_delete<T>(world: World, entity: Entity<T>)
|
||||||
local entity_index = world.entity_index
|
|
||||||
local record = inner_entity_index_try_get_unsafe(entity::number)
|
local record = inner_entity_index_try_get_unsafe(entity::number)
|
||||||
if not record then
|
if not record then
|
||||||
return
|
return
|
||||||
|
@ -2904,21 +2915,19 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local dense_array = entity_index.dense_array
|
|
||||||
local dense = record.dense
|
local dense = record.dense
|
||||||
local i_swap = entity_index.alive_count
|
local i_swap = entity_index.alive_count
|
||||||
entity_index.alive_count = i_swap - 1
|
entity_index.alive_count = i_swap - 1
|
||||||
|
|
||||||
local e_swap = dense_array[i_swap]
|
local e_swap = eindex_dense_array[i_swap]
|
||||||
local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record
|
local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record
|
||||||
|
|
||||||
r_swap.dense = dense
|
r_swap.dense = dense
|
||||||
record.archetype = nil :: any
|
record.archetype = nil :: any
|
||||||
record.row = nil :: any
|
record.row = nil :: any
|
||||||
record.dense = i_swap
|
record.dense = i_swap
|
||||||
|
|
||||||
dense_array[dense] = e_swap
|
eindex_dense_array[dense] = e_swap
|
||||||
dense_array[i_swap] = ECS_GENERATION_INC(entity :: number)
|
eindex_dense_array[i_swap] = ECS_GENERATION_INC(entity :: number)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function inner_world_exists<T>(world: World, entity: Entity<T>): boolean
|
local function inner_world_exists<T>(world: World, entity: Entity<T>): boolean
|
||||||
|
@ -3066,6 +3075,8 @@ return {
|
||||||
Remove = (EcsRemove :: any) :: Entity,
|
Remove = (EcsRemove :: any) :: Entity,
|
||||||
Name = (EcsName :: any) :: Entity<string>,
|
Name = (EcsName :: any) :: Entity<string>,
|
||||||
Exclusive = EcsExclusive :: Entity,
|
Exclusive = EcsExclusive :: Entity,
|
||||||
|
ArchetypeCreate = EcsOnArchetypeCreate,
|
||||||
|
ArchetypeDelete = EcsOnArchetypeDelete,
|
||||||
Rest = (EcsRest :: any) :: Entity,
|
Rest = (EcsRest :: any) :: Entity,
|
||||||
|
|
||||||
pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
|
pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.8.1",
|
"version": "0.8.2",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "jecs.luau",
|
"main": "jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -659,7 +659,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()
|
||||||
|
|
||||||
|
@ -846,6 +845,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()
|
||||||
|
@ -876,8 +885,94 @@ TEST("world:each()", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
FOCUS()
|
||||||
TEST("world:range()", function()
|
TEST("world:range()", function()
|
||||||
|
|
||||||
|
do CASE "spawn entity under min range"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(400, 1000)
|
||||||
|
CHECK(world.entity_index.alive_count == 399)
|
||||||
|
local e = world:entity(300)
|
||||||
|
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::number)< 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::number) == 1000)
|
||||||
|
|
||||||
|
local e1v1 = server:entity()
|
||||||
|
local e4 = client:entity(e1v1)
|
||||||
|
CHECK(ECS_ID(e4::number) == e1)
|
||||||
|
CHECK(ECS_GENERATION(e4::number) == 1)
|
||||||
|
CHECK(not client:contains(e2))
|
||||||
|
CHECK(client:contains(e4))
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "under range start"
|
do CASE "under range start"
|
||||||
|
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
world:range(400, 1000)
|
world:range(400, 1000)
|
||||||
local id = world:entity() :: number
|
local id = world:entity() :: number
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.8.1"
|
version = "0.8.2"
|
||||||
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