Compare commits

...

4 commits

Author SHA1 Message Date
Ukendio
f8b3772bce Fix spelling error
Some checks are pending
Analysis / Run Luau Analyze (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Unit Testing / Run Luau Tests (push) Waiting to run
2024-12-27 04:50:15 +01:00
Ukendio
fad88419a3 Fix type errors 2024-12-27 04:34:17 +01:00
Ukendio
1c8a4967e3 Bump patch 2024-12-27 03:47:43 +01:00
Ukendio
b14e984c66 Fix shadowed variable 2024-12-27 03:47:02 +01:00
2 changed files with 234 additions and 233 deletions

453
jecs.luau
View file

@ -78,32 +78,32 @@ type EntityIndex = {
local HI_COMPONENT_ID = _G.__JECS_HI_COMPONENT_ID or 256 local HI_COMPONENT_ID = _G.__JECS_HI_COMPONENT_ID or 256
-- stylua: ignore start -- stylua: ignore start
local EcsOnAdd = HI_COMPONENT_ID + 1 local EcsOnAdd = HI_COMPONENT_ID + 1
local EcsOnRemove = HI_COMPONENT_ID + 2 local EcsOnRemove = HI_COMPONENT_ID + 2
local EcsOnSet = HI_COMPONENT_ID + 3 local EcsOnSet = HI_COMPONENT_ID + 3
local EcsWildcard = HI_COMPONENT_ID + 4 local EcsWildcard = HI_COMPONENT_ID + 4
local EcsChildOf = HI_COMPONENT_ID + 5 local EcsChildOf = HI_COMPONENT_ID + 5
local EcsComponent = HI_COMPONENT_ID + 6 local EcsComponent = HI_COMPONENT_ID + 6
local EcsOnDelete = HI_COMPONENT_ID + 7 local EcsOnDelete = HI_COMPONENT_ID + 7
local EcsOnDeleteTarget = HI_COMPONENT_ID + 8 local EcsOnDeleteTarget = HI_COMPONENT_ID + 8
local EcsDelete = HI_COMPONENT_ID + 9 local EcsDelete = HI_COMPONENT_ID + 9
local EcsRemove = HI_COMPONENT_ID + 10 local EcsRemove = HI_COMPONENT_ID + 10
local EcsName = HI_COMPONENT_ID + 11 local EcsName = HI_COMPONENT_ID + 11
local EcsArchetypeCreate = HI_COMPONENT_ID + 12 local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12
local EcsArchetypeDelete = HI_COMPONENT_ID + 13 local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
local EcsRest = HI_COMPONENT_ID + 14 local EcsRest = HI_COMPONENT_ID + 14
local ECS_PAIR_FLAG = 0x8 local ECS_PAIR_FLAG = 0x8
local ECS_ID_FLAGS_MASK = 0x10 local ECS_ID_FLAGS_MASK = 0x10
local ECS_ENTITY_MASK = bit32.lshift(1, 24) local ECS_ENTITY_MASK = bit32.lshift(1, 24)
local ECS_GENERATION_MASK = bit32.lshift(1, 16) local ECS_GENERATION_MASK = bit32.lshift(1, 16)
local ECS_ID_DELETE = 0b0000_0001 local ECS_ID_DELETE = 0b0000_0001
local ECS_ID_IS_TAG = 0b0000_0010 local ECS_ID_IS_TAG = 0b0000_0010
local ECS_ID_HAS_ON_ADD = 0b0000_0100 local ECS_ID_HAS_ON_ADD = 0b0000_0100
local ECS_ID_HAS_ON_SET = 0b0000_1000 local ECS_ID_HAS_ON_SET = 0b0000_1000
local ECS_ID_HAS_ON_REMOVE = 0b0001_0000 local ECS_ID_HAS_ON_REMOVE = 0b0001_0000
local ECS_ID_MASK = 0b0000_0000 local ECS_ID_MASK = 0b0000_0000
-- stylua: ignore end -- stylua: ignore end
local NULL_ARRAY = table.freeze({}) :: Column local NULL_ARRAY = table.freeze({}) :: Column
@ -611,6 +611,7 @@ local function archetype_create(world: World, id_types: { i24 }, ty, prev: i53?)
local idr_t = id_record_ensure(world, t) local idr_t = id_record_ensure(world, t)
archetype_append_to_records(idr_t, archetype_id, records, t, i) archetype_append_to_records(idr_t, archetype_id, records, t, i)
end end
if bit32.band(idr.flags, ECS_ID_IS_TAG) == 0 then if bit32.band(idr.flags, ECS_ID_IS_TAG) == 0 then
columns[i] = {} columns[i] = {}
else else
@ -619,7 +620,7 @@ local function archetype_create(world: World, id_types: { i24 }, ty, prev: i53?)
end end
for _, id in id_types do for _, id in id_types do
local observer_list = find_observers(world, EcsArchetypeCreate, id) local observer_list = find_observers(world, EcsOnArchetypeCreate, id)
if not observer_list then if not observer_list then
continue continue
end end
@ -1052,7 +1053,7 @@ local function archetype_destroy(world: World, archetype: Archetype)
local records = archetype.records local records = archetype.records
for id in records do for id in records do
local observer_list = find_observers(world, EcsArchetypeDelete, id) local observer_list = find_observers(world, EcsOnArchetypeDelete, id)
if not observer_list then if not observer_list then
continue continue
end end
@ -1457,7 +1458,6 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row]
end end
local records = archetype.records
for j, id in ids do for j, id in ids do
queryOutput[j] = columns[records[id].column][row] queryOutput[j] = columns[records[id].column][row]
end end
@ -1553,10 +1553,10 @@ local function query_cached(query: QueryInner)
-- because the event will be emitted for all components of that Archetype. -- because the event will be emitted for all components of that Archetype.
local first = query.ids[1] local first = query.ids[1]
local observerable = world.observerable local observerable = world.observerable
local on_create_action = observerable[EcsArchetypeCreate] local on_create_action = observerable[EcsOnArchetypeCreate]
if not on_create_action then if not on_create_action then
on_create_action = {} on_create_action = {}
observerable[EcsArchetypeCreate] = on_create_action observerable[EcsOnArchetypeCreate] = on_create_action
end end
local query_cache_on_create = on_create_action[first] local query_cache_on_create = on_create_action[first]
if not query_cache_on_create then if not query_cache_on_create then
@ -1564,10 +1564,10 @@ local function query_cached(query: QueryInner)
on_create_action[first] = query_cache_on_create on_create_action[first] = query_cache_on_create
end end
local on_delete_action = observerable[EcsArchetypeDelete] local on_delete_action = observerable[EcsOnArchetypeDelete]
if not on_delete_action then if not on_delete_action then
on_delete_action = {} on_delete_action = {}
observerable[EcsArchetypeDelete] = on_delete_action observerable[EcsOnArchetypeDelete] = on_delete_action
end end
local query_cache_on_delete = on_delete_action[first] local query_cache_on_delete = on_delete_action[first]
if not query_cache_on_delete then if not query_cache_on_delete then
@ -1609,181 +1609,11 @@ local function query_cached(query: QueryInner)
local world_query_iter_next local world_query_iter_next
local columns: { Column } local columns: { Column }
local entities: { i53 } local entities: { number }
local i: number local i: number
local archetype: Archetype local archetype: Archetype
local records: { ArchetypeRecord } local records: { ArchetypeRecord }
if not B then
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
records = archetype.records
a = columns[records[A].column]
end
local row = i
i -= 1
return entityId, a[row]
end
elseif not C then
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
local records = archetype.records
a = columns[records[A].column]
b = columns[records[B].column]
end
local row = i
i -= 1
return entityId, a[row], b[row]
end
elseif not D then
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
local records = archetype.records
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
end
local row = i
i -= 1
return entityId, a[row], b[row], c[row]
end
elseif not E then
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
local records = archetype.records
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
end
local row = i
i -= 1
return entityId, a[row], b[row], c[row], d[row]
end
else
local queryOutput = {}
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
local records = archetype.records
if not F then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
elseif not G then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
elseif not H then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
g = columns[records[G].column]
elseif not I then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
g = columns[records[G].column]
h = columns[records[H].column]
end
end
local row = i
i -= 1
if not F then
return entityId, a[row], b[row], c[row], d[row], e[row]
elseif not G then
return entityId, a[row], b[row], c[row], d[row], e[row], f[row]
elseif not H then
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
elseif not I then
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row]
end
local records = archetype.records
for j, id in ids do
queryOutput[j] = columns[records[id].column][row]
end
return entityId, unpack(queryOutput)
end
end
local function cached_query_iter() local function cached_query_iter()
lastArchetype = 1 lastArchetype = 1
archetype = compatible_archetypes[lastArchetype] archetype = compatible_archetypes[lastArchetype]
@ -1843,6 +1673,175 @@ local function query_cached(query: QueryInner)
return world_query_iter_next return world_query_iter_next
end end
if not B then
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
records = archetype.records
a = columns[records[A].column]
end
local row = i
i -= 1
return entityId, a[row]
end
elseif not C then
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
records = archetype.records
a = columns[records[A].column]
b = columns[records[B].column]
end
local row = i
i -= 1
return entityId, a[row], b[row]
end
elseif not D then
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
records = archetype.records
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
end
local row = i
i -= 1
return entityId, a[row], b[row], c[row]
end
elseif not E then
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
records = archetype.records
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
end
local row = i
i -= 1
return entityId, a[row], b[row], c[row], d[row]
end
else
local queryOutput = {}
function world_query_iter_next(): any
local entityId = entities[i]
while entityId == nil do
lastArchetype += 1
archetype = compatible_archetypes[lastArchetype]
if not archetype then
return nil
end
entities = archetype.entities
i = #entities
entityId = entities[i]
columns = archetype.columns
records = archetype.records
if not F then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
elseif not G then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
elseif not H then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
g = columns[records[G].column]
elseif not I then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
g = columns[records[G].column]
h = columns[records[H].column]
end
end
local row = i
i -= 1
if not F then
return entityId, a[row], b[row], c[row], d[row], e[row]
elseif not G then
return entityId, a[row], b[row], c[row], d[row], e[row], f[row]
elseif not H then
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
elseif not I then
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row]
end
for j, id in ids do
queryOutput[j] = columns[records[id].column][row]
end
return entityId, unpack(queryOutput)
end
end
local cached_query = query :: any local cached_query = query :: any
cached_query.archetypes = query_archetypes cached_query.archetypes = query_archetypes
cached_query.__iter = cached_query_iter cached_query.__iter = cached_query_iter
@ -2142,16 +2141,26 @@ function World.new()
return self return self
end end
export type Id<T = unknown> = Entity<T> type Id<T = unknown> =
| (number & { __jecs_pair_value: T })
| (number & { __T: T })
type function ecs_entity_t(entity) export type Pair<P = Entity, O = Entity> = number & {
return entity:components()[2]:readproperty(types.singleton("__T")) __jecs_pair_value: ecs_id_t<ecs_pair_t<P, O>>
}
type function ecs_id_t(entity)
local ty = entity:components()[2]
local __T = ty:readproperty(types.singleton("__T"))
if not __T then
return ty:readproperty(types.singleton("__jecs_pair_value"))
end
return __T
end end
type function Pair(first, second) type function ecs_pair_t(first, second)
local thing = first:components()[2] local ty = first:components()[2]
if ty:readproperty(types.singleton("__T")):is("nil") then
if thing:readproperty(types.singleton("__T")):is("nil") then
return second return second
else else
return first return first
@ -2160,7 +2169,7 @@ end
type Item<T...> = (self: Query<T...>) -> (Entity, T...) type Item<T...> = (self: Query<T...>) -> (Entity, T...)
export type Entity<T = unknown> = number & { __T: T } export type Entity<T = nil> = number & { __T: T }
type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...) type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
@ -2188,14 +2197,6 @@ type Observer = {
query: QueryInner, query: QueryInner,
} }
type function ecs_partial_t(ty)
local output = types.newtable()
for k, v in ty:properties() do
output:setproperty(k, types.unionof(v.write, types.singleton(nil)))
end
return output
end
export type World = { export type World = {
archetypeIndex: { [string]: Archetype }, archetypeIndex: { [string]: Archetype },
archetypes: Archetypes, archetypes: Archetypes,
@ -2250,14 +2251,14 @@ export type World = {
children: (self: World, id: Id) -> () -> Entity, children: (self: World, id: Id) -> () -> Entity,
--- Searches the world for entities that match a given query --- Searches the world for entities that match a given query
query: (<A>(World, A) -> Query<ecs_entity_t<A>>) query: (<A>(World, A) -> Query<ecs_id_t<A>>)
& (<A, B>(World, A, B) -> Query<ecs_entity_t<A>, ecs_entity_t<B>>) & (<A, B>(World, A, B) -> Query<ecs_id_t<A>, ecs_id_t<B>>)
& (<A, B, C>(World, A, B, C) -> Query<ecs_entity_t<A>, ecs_entity_t<B>, ecs_entity_t<C>>) & (<A, B, C>(World, A, B, C) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>>)
& (<A, B, C, D>(World, A, B, C, D) -> Query<ecs_entity_t<A>, ecs_entity_t<B>, ecs_entity_t<C>, ecs_entity_t<D>>) & (<A, B, C, D>(World, A, B, C, D) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>>)
& (<A, B, C, D, E>(World, A, B, C, D, E) -> Query<ecs_entity_t<A>, ecs_entity_t<B>, ecs_entity_t<C>, ecs_entity_t<D>, ecs_entity_t<E>>) & (<A, B, C, D, E>(World, A, B, C, D, E) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>, ecs_id_t<E>>)
& (<A, B, C, D, E, F>(World, A, B, C, D, E, F) -> Query<ecs_entity_t<A>, ecs_entity_t<B>, ecs_entity_t<C>, ecs_entity_t<D>, ecs_entity_t<E>, ecs_entity_t<F>>) & (<A, B, C, D, E, F>(World, A, B, C, D, E, F) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>, ecs_id_t<E>, ecs_id_t<F>>)
& (<A, B, C, D, E, F, G>(World, A, B, C, D, E, F, G) -> Query<ecs_entity_t<A>, ecs_entity_t<B>, ecs_entity_t<C>, ecs_entity_t<D>, ecs_entity_t<E>, ecs_entity_t<F>, ecs_entity_t<G>>) & (<A, B, C, D, E, F, G>(World, A, B, C, D, E, F, G) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>, ecs_id_t<E>, ecs_id_t<F>, ecs_id_t<G>>)
& (<A, B, C, D, E, F, G, H>(World, A, B, C, D, E, F, G, H) -> Query<ecs_entity_t<A>, ecs_entity_t<B>, ecs_entity_t<C>, ecs_entity_t<D>, ecs_entity_t<E>, ecs_entity_t<F>, ecs_entity_t<G>, ecs_entity_t<H>>) & (<A, B, C, D, E, F, G, H>(World, A, B, C, D, E, F, G, H) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>, ecs_id_t<E>, ecs_id_t<F>, ecs_id_t<G>, ecs_id_t<H>>)
} }
return { return {

View file

@ -1,15 +1,15 @@
[package] [package]
name = "ukendio/jecs" name = "ukendio/jecs"
version = "0.5.0" version = "0.5.1"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
license = "MIT" license = "MIT"
include = [ include = [
"default.project.json", "default.project.json",
"jecs.luau", "jecs.luau",
"wally.toml", "wally.toml",
"README.md", "README.md",
"CHANGELOG.md", "CHANGELOG.md",
"LICENSE", "LICENSE",
] ]
exclude = ["**"] exclude = ["**"]