mirror of
https://github.com/Ukendio/jecs.git
synced 2025-09-23 16:39:17 +00:00
Compare commits
No commits in common. "af093713b4d82f0b2be926a6dd51083d8b736242" and "29a66d92c2a92e0ee6a6b246fd3e5c0817aeee65" have entirely different histories.
af093713b4
...
29a66d92c2
11 changed files with 420 additions and 609 deletions
|
@ -199,7 +199,6 @@ do
|
||||||
end
|
end
|
||||||
|
|
||||||
local q = world:query(A, B, C, D)
|
local q = world:query(A, B, C, D)
|
||||||
q:archetypes()
|
|
||||||
START()
|
START()
|
||||||
for id in q do
|
for id in q do
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,22 +11,15 @@ end
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local mirror = require("@mirror")
|
local mirror = require("@mirror")
|
||||||
|
|
||||||
|
type i53 = number
|
||||||
|
|
||||||
do
|
do
|
||||||
TITLE(testkit.color.white_underline("Jecs query"))
|
TITLE(testkit.color.white_underline("Jecs query"))
|
||||||
local ecs = jecs.world() :: jecs.World
|
local ecs = jecs.world()
|
||||||
do
|
do
|
||||||
TITLE("one component in common")
|
TITLE("one component in common")
|
||||||
|
|
||||||
local function view_bench(world: jecs.World,
|
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
|
||||||
A: jecs.Id,
|
|
||||||
B: jecs.Id,
|
|
||||||
C: jecs.Id,
|
|
||||||
D: jecs.Id,
|
|
||||||
E: jecs.Id,
|
|
||||||
F: jecs.Id,
|
|
||||||
G: jecs.Id,
|
|
||||||
H: jecs.Id
|
|
||||||
)
|
|
||||||
BENCH("1 component", function()
|
BENCH("1 component", function()
|
||||||
for _ in world:query(A) do
|
for _ in world:query(A) do
|
||||||
end
|
end
|
||||||
|
@ -138,21 +131,11 @@ end
|
||||||
|
|
||||||
do
|
do
|
||||||
TITLE(testkit.color.white_underline("Mirror query"))
|
TITLE(testkit.color.white_underline("Mirror query"))
|
||||||
local ecs = mirror.World.new() :: jecs.World
|
local ecs = mirror.World.new()
|
||||||
do
|
do
|
||||||
TITLE("one component in common")
|
TITLE("one component in common")
|
||||||
|
|
||||||
local function view_bench(
|
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
|
||||||
world: mirror.World,
|
|
||||||
A: jecs.Id,
|
|
||||||
B: jecs.Id,
|
|
||||||
C: jecs.Id,
|
|
||||||
D: jecs.Id,
|
|
||||||
E: jecs.Id,
|
|
||||||
F: jecs.Id,
|
|
||||||
G: jecs.Id,
|
|
||||||
H: jecs.Id
|
|
||||||
)
|
|
||||||
BENCH("1 component", function()
|
BENCH("1 component", function()
|
||||||
for _ in world:query(A) do
|
for _ in world:query(A) do
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local world = jecs.world()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Position = world:component() :: jecs.Id<vector>
|
local Position = world:component()
|
||||||
local Walking = world:component()
|
local Walking = world:component()
|
||||||
local Name = world:component() :: jecs.Id<string>
|
local Name = world:component()
|
||||||
|
|
||||||
local function name(e: jecs.Entity): string
|
|
||||||
return assert(world:get(e, Name))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create an entity with name Bob
|
-- Create an entity with name Bob
|
||||||
local bob = world:entity()
|
local bob = world:entity()
|
||||||
|
|
||||||
-- The set operation finds or creates a component, and sets it.
|
-- The set operation finds or creates a component, and sets it.
|
||||||
world:set(bob, Position, vector.create(10, 20, 30))
|
world:set(bob, Position, Vector3.new(10, 20, 30))
|
||||||
-- Name the entity Bob
|
-- Name the entity Bob
|
||||||
world:set(bob, Name, "Bob")
|
world:set(bob, Name, "Bob")
|
||||||
-- The add operation adds a component without setting a value. This is
|
-- The add operation adds a component without setting a value. This is
|
||||||
|
@ -22,16 +18,15 @@ world:add(bob, Walking)
|
||||||
|
|
||||||
-- Get the value for the Position component
|
-- Get the value for the Position component
|
||||||
local pos = world:get(bob, Position)
|
local pos = world:get(bob, Position)
|
||||||
assert(pos)
|
print(`\{{pos.X}, {pos.Y}, {pos.Z}\}`)
|
||||||
print(`\{{pos.x}, {pos.y}, {pos.z}\}`)
|
|
||||||
|
|
||||||
-- Overwrite the value of the Position component
|
-- Overwrite the value of the Position component
|
||||||
world:set(bob, Position, vector.create(40, 50, 60))
|
world:set(bob, Position, Vector3.new(40, 50, 60))
|
||||||
|
|
||||||
local alice = world:entity()
|
local alice = world:entity()
|
||||||
-- Create another named entity
|
-- Create another named entity
|
||||||
world:set(alice, Name, "Alice")
|
world:set(alice, Name, "Alice")
|
||||||
world:set(alice, Position, vector.create(10, 20, 30))
|
world:set(alice, Position, Vector3.new(10, 20, 30))
|
||||||
world:add(alice, Walking)
|
world:add(alice, Walking)
|
||||||
|
|
||||||
-- Remove tag
|
-- Remove tag
|
||||||
|
@ -39,7 +34,7 @@ world:remove(alice, Walking)
|
||||||
|
|
||||||
-- Iterate all entities with Position
|
-- Iterate all entities with Position
|
||||||
for entity, p in world:query(Position) do
|
for entity, p in world:query(Position) do
|
||||||
print(`{name(entity)}: \{{p.x}, {p.y}, {p.z}\}`)
|
print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Output:
|
-- Output:
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
-- Using world:target is the recommended way to grab the target in a wildcard
|
|
||||||
-- query. However the random access can add up in very hot paths. Accessing its
|
|
||||||
-- column can drastically improve performance, especially when there are
|
|
||||||
-- multiple adjacent pairs.
|
|
||||||
|
|
||||||
local jecs = require("@jecs")
|
|
||||||
local pair = jecs.pair
|
|
||||||
local __ = jecs.Wildcard
|
|
||||||
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
local Likes = world:entity()
|
|
||||||
local function name(e, name: string): string
|
|
||||||
if name then
|
|
||||||
world:set(e, jecs.Name, name)
|
|
||||||
return name
|
|
||||||
end
|
|
||||||
return assert(world:get(e, jecs.Name))
|
|
||||||
end
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
name(e1, "e1")
|
|
||||||
local e2 = world:entity()
|
|
||||||
name(e2, "e2")
|
|
||||||
local e3 = world:entity()
|
|
||||||
name(e3, "e3")
|
|
||||||
|
|
||||||
world:add(e1, pair(Likes, e2))
|
|
||||||
world:add(e1, pair(Likes, e3))
|
|
||||||
|
|
||||||
local likes = jecs.component_record(world, pair(Likes, __))
|
|
||||||
assert(likes)
|
|
||||||
|
|
||||||
local likes_cr = likes.records
|
|
||||||
local likes_counts = likes.counts
|
|
||||||
|
|
||||||
local archetypes = world:query(pair(Likes, __)):archetypes()
|
|
||||||
|
|
||||||
for _, archetype in archetypes do
|
|
||||||
local types = archetype.types
|
|
||||||
|
|
||||||
-- Get the starting index which is what the (R, *) alias is at
|
|
||||||
local wc = likes_cr[archetype.id]
|
|
||||||
local count = likes_counts[archetype.id]
|
|
||||||
|
|
||||||
local entities = archetype.entities
|
|
||||||
for i = #entities, 1, -1 do
|
|
||||||
-- It is generally a good idea to iterate backwards on arrays if you
|
|
||||||
-- need to delete the iterated entity to prevent iterator invalidation
|
|
||||||
local entity = entities[i]
|
|
||||||
for cr = wc, wc + count - 1 do
|
|
||||||
local person = jecs.pair_second(world, types[cr])
|
|
||||||
print(`entity ${entity} likes ${person}`)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Output:
|
|
||||||
-- entity $273 likes $275
|
|
||||||
-- entity $273 likes $274
|
|
|
@ -1,44 +0,0 @@
|
||||||
-- To get the most out of performance, you can lift the inner loop of queries to
|
|
||||||
-- the system in which you can do archetype-specific optimizations like finding
|
|
||||||
-- the parent once per archetype rather than per entity.
|
|
||||||
|
|
||||||
local jecs = require("@jecs")
|
|
||||||
local pair = jecs.pair
|
|
||||||
local ChildOf = jecs.ChildOf
|
|
||||||
local __ = jecs.Wildcard
|
|
||||||
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
local Position = world:component() :: jecs.Id<vector>
|
|
||||||
local Visible = world:entity()
|
|
||||||
|
|
||||||
local parent = world:entity()
|
|
||||||
world:set(parent, Position, vector.zero)
|
|
||||||
world:add(parent, Visible)
|
|
||||||
|
|
||||||
local child = world:entity()
|
|
||||||
world:set(child, Position, vector.one)
|
|
||||||
world:add(child, pair(ChildOf, parent))
|
|
||||||
|
|
||||||
local parents = jecs.component_record(world, pair(ChildOf, __))
|
|
||||||
assert(parents)
|
|
||||||
|
|
||||||
local parent_cr = parents.records
|
|
||||||
|
|
||||||
local archetypes = world:query(Position, pair(ChildOf, __)):archetypes()
|
|
||||||
|
|
||||||
for _, archetype in archetypes do
|
|
||||||
local types = archetype.types
|
|
||||||
local p = jecs.pair_second(world, types[parent_cr[archetype.id]])
|
|
||||||
if world:has(p, Visible) then
|
|
||||||
local columns = archetype.columns_map
|
|
||||||
local positions = columns[Position]
|
|
||||||
for row, entity in archetype.entities do
|
|
||||||
local pos = positions[row]
|
|
||||||
print(`Child ${entity} of ${p} is visible at {pos}`)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Output:
|
|
||||||
-- Child $274 of $273 is visibile at 1,1,1
|
|
|
@ -1,5 +1,5 @@
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local world = jecs.world()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Position = world:component()
|
local Position = world:component()
|
||||||
local Velocity = world:component()
|
local Velocity = world:component()
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local pair = jecs.pair
|
local pair = jecs.pair
|
||||||
|
|
||||||
local world = jecs.world()
|
local world = jecs.World.new()
|
||||||
local Name = world:component() :: jecs.Id<string>
|
local Name = world:component()
|
||||||
|
|
||||||
|
local function named(ctr, name)
|
||||||
local function name(e: jecs.Entity): string
|
local e = ctr(world)
|
||||||
return assert(world:get(e, Name))
|
world:set(e, Name, name)
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
local function name(e)
|
||||||
|
return world:get(e, Name)
|
||||||
end
|
end
|
||||||
|
|
||||||
local Position = world:component() :: jecs.Id<vector>
|
local Position = named(world.component, "Position") :: jecs.Entity<vector>
|
||||||
world:set(Position, Name, "Position")
|
|
||||||
local Previous = jecs.Rest
|
local Previous = jecs.Rest
|
||||||
|
|
||||||
local added = world
|
local added = world
|
||||||
|
@ -26,14 +29,10 @@ local removed = world
|
||||||
:cached()
|
:cached()
|
||||||
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
local e1 = named(world.entity, "e1")
|
||||||
world:set(e1, Name, "e1")
|
|
||||||
world:set(e1, Position, vector.create(10, 20, 30))
|
world:set(e1, Position, vector.create(10, 20, 30))
|
||||||
|
local e2 = named(world.entity, "e2")
|
||||||
local e2 = world:entity()
|
|
||||||
world:set(e2, Name, "e2")
|
|
||||||
world:set(e2, Position, vector.create(10, 20, 30))
|
world:set(e2, Position, vector.create(10, 20, 30))
|
||||||
|
|
||||||
for entity, p in added do
|
for entity, p in added do
|
||||||
print(`Added {name(entity)}: \{{p.x}, {p.y}, {p.z}}`)
|
print(`Added {name(entity)}: \{{p.x}, {p.y}, {p.z}}`)
|
||||||
world:set(entity, pair(Previous, Position), p)
|
world:set(entity, pair(Previous, Position), p)
|
||||||
|
@ -41,10 +40,10 @@ end
|
||||||
|
|
||||||
world:set(e1, Position, vector.create(999, 999, 1998))
|
world:set(e1, Position, vector.create(999, 999, 1998))
|
||||||
|
|
||||||
for entity, new, old in changed do
|
for _, archetype in changed:archetypes() do
|
||||||
if new ~= old then
|
if new ~= old then
|
||||||
print(`{name(entity)}'s Position changed from \{{old.x}, {old.y}, {old.z}\} to \{{new.x}, {new.y}, {new.z}\}`)
|
print(`{name(e)}'s Position changed from \{{old.x}, {old.y}, {old.z}\} to \{{new.x}, {new.y}, {new.z}\}`)
|
||||||
world:set(entity, pair(Previous, Position), new)
|
world:set(e, pair(Previous, Position), new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,47 +3,32 @@ local pair = jecs.pair
|
||||||
local ChildOf = jecs.ChildOf
|
local ChildOf = jecs.ChildOf
|
||||||
local __ = jecs.Wildcard
|
local __ = jecs.Wildcard
|
||||||
local Name = jecs.Name
|
local Name = jecs.Name
|
||||||
local world = jecs.world()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Voxel = world:component() :: jecs.Id
|
type Id<T = nil> = number & { __T: T }
|
||||||
local Position = world:component() :: jecs.Id<vector>
|
local Voxel = world:component() :: Id
|
||||||
local Perception = world:component() :: jecs.Id<{
|
local Position = world:component() :: Id<Vector3>
|
||||||
|
local Perception = world:component() :: Id<{
|
||||||
range: number,
|
range: number,
|
||||||
fov: number,
|
fov: number,
|
||||||
dir: vector,
|
dir: Vector3,
|
||||||
}>
|
}>
|
||||||
type part = {
|
local PrimaryPart = world:component() :: Id<Part>
|
||||||
Position: vector
|
|
||||||
}
|
|
||||||
local PrimaryPart = world:component() :: jecs.Id<part>
|
|
||||||
|
|
||||||
local local_player = {
|
local local_player = game:GetService("Players").LocalPlayer
|
||||||
Character = {
|
|
||||||
PrimaryPart = {
|
|
||||||
Position = vector.create(50, 0, 30)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
local workspace = {
|
|
||||||
CurrentCamera = {
|
|
||||||
CFrame = {
|
|
||||||
LookVector = vector.create(0, 0, -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local function distance(a: vector, b: vector)
|
local function distance(a: Vector3, b: Vector3)
|
||||||
return vector.magnitude((b - a))
|
return (b - a).Magnitude
|
||||||
end
|
end
|
||||||
|
|
||||||
local function is_in_fov(a: vector, b: vector, forward_dir: vector, fov_angle: number)
|
local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, fov_angle: number)
|
||||||
local to_target = b - a
|
local to_target = b - a
|
||||||
|
|
||||||
local forward_xz = vector.normalize(vector.create(forward_dir.x, 0, forward_dir.z))
|
local forward_xz = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit
|
||||||
local to_target_xz = vector.normalize(vector.create(to_target.x, 0, to_target.z))
|
local to_target_xz = Vector3.new(to_target.X, 0, to_target.Z).Unit
|
||||||
|
|
||||||
local angle_to_target = math.deg(math.atan2(to_target_xz.z, to_target_xz.x))
|
local angle_to_target = math.deg(math.atan2(to_target_xz.Z, to_target_xz.X))
|
||||||
local forward_angle = math.deg(math.atan2(forward_xz.z, forward_xz.z))
|
local forward_angle = math.deg(math.atan2(forward_xz.Z, forward_xz.X))
|
||||||
|
|
||||||
local angle_difference = math.abs(forward_angle - angle_to_target)
|
local angle_difference = math.abs(forward_angle - angle_to_target)
|
||||||
|
|
||||||
|
@ -57,7 +42,7 @@ end
|
||||||
local map = {}
|
local map = {}
|
||||||
local grid = 50
|
local grid = 50
|
||||||
|
|
||||||
local function add_to_voxel(source: jecs.Entity, position: vector, prev_voxel_id: jecs.Entity?)
|
local function add_to_voxel(source: number, position: Vector3, prev_voxel_id: number?)
|
||||||
local hash = position // grid
|
local hash = position // grid
|
||||||
local voxel_id = map[hash]
|
local voxel_id = map[hash]
|
||||||
if not voxel_id then
|
if not voxel_id then
|
||||||
|
@ -94,7 +79,7 @@ local function update_camera_direction(dt: number)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function perceive_enemies(dt: number)
|
local function perceive_enemies(dt: number)
|
||||||
local it = world:query(Perception, Position, PrimaryPart):iter()
|
local it = world:query(Perception, Position, PrimaryPart)
|
||||||
-- There is only going to be one entity matching the query
|
-- There is only going to be one entity matching the query
|
||||||
local e, self_perception, self_position, self_primary_part = it()
|
local e, self_perception, self_position, self_primary_part = it()
|
||||||
|
|
||||||
|
@ -108,28 +93,28 @@ local function perceive_enemies(dt: number)
|
||||||
|
|
||||||
if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then
|
if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then
|
||||||
local p = target_position
|
local p = target_position
|
||||||
print(`Entity {world:get(e, Name)} can see target {world:get(enemy, Name)} at ({p.x}, {p.y}, {p.z})`)
|
print(`Entity {world:get(e, Name)} can see target {world:get(enemy, Name)} at ({p.X}, {p.Y}, {p.Z})`)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local player = world:entity()
|
local player = world:entity()
|
||||||
world:set(player, Perception, {
|
world:set(player, Perception, {
|
||||||
range = 200,
|
range = 100,
|
||||||
fov = 90,
|
fov = 90,
|
||||||
dir = vector.create(1, 0, 0),
|
dir = Vector3.new(1, 0, 0),
|
||||||
})
|
})
|
||||||
world:set(player, Name, "LocalPlayer")
|
world:set(player, Name, "LocalPlayer")
|
||||||
local primary_part = local_player.Character.PrimaryPart
|
local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
|
||||||
world:set(player, PrimaryPart, primary_part)
|
world:set(player, PrimaryPart, primary_part)
|
||||||
world:set(player, Position, vector.zero)
|
world:set(player, Position, Vector3.zero)
|
||||||
|
|
||||||
local enemy = world:entity()
|
local enemy = world:entity()
|
||||||
world:set(enemy, Name, "Enemy $1")
|
world:set(enemy, Name, "Enemy $1")
|
||||||
world:set(enemy, Position, vector.create(50, 0, 20))
|
world:set(enemy, Position, Vector3.new(50, 0, 20))
|
||||||
|
|
||||||
add_to_voxel(player, primary_part.Position)
|
add_to_voxel(player, primary_part.Position)
|
||||||
add_to_voxel(enemy, assert(world:get(enemy, Position)))
|
add_to_voxel(enemy, world)
|
||||||
|
|
||||||
local dt = 1 / 60
|
local dt = 1 / 60
|
||||||
reconcile_client_owned_assembly_to_voxel(dt)
|
reconcile_client_owned_assembly_to_voxel(dt)
|
||||||
|
|
5
jecs.d.ts
vendored
5
jecs.d.ts
vendored
|
@ -184,11 +184,6 @@ export class World {
|
||||||
*/
|
*/
|
||||||
cleanup(): void;
|
cleanup(): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all instances of specified component
|
|
||||||
*/
|
|
||||||
// purge<T>(component: Id<T>): void
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all components and relationships from the given entity, but
|
* Clears all components and relationships from the given entity, but
|
||||||
* does not delete the entity from the world.
|
* does not delete the entity from the world.
|
||||||
|
|
460
jecs.luau
460
jecs.luau
|
@ -54,8 +54,6 @@ export type Query<T...> = typeof(setmetatable(
|
||||||
archetypes: (self: Query<T...>) -> { Archetype },
|
archetypes: (self: Query<T...>) -> { Archetype },
|
||||||
cached: (self: Query<T...>) -> Query<T...>,
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
ids: { Id<any> },
|
ids: { Id<any> },
|
||||||
filter_with: { Id<any> }?,
|
|
||||||
filter_without: { Id<any> }?
|
|
||||||
-- world: World
|
-- world: World
|
||||||
},
|
},
|
||||||
{} :: {
|
{} :: {
|
||||||
|
@ -94,14 +92,13 @@ type archetype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type componentrecord = {
|
type componentrecord = {
|
||||||
component: i53,
|
|
||||||
records: { [number]: number },
|
records: { [number]: number },
|
||||||
counts: { [i53]: number },
|
counts: { [i53]: number },
|
||||||
flags: number,
|
flags: number,
|
||||||
size: number,
|
size: number,
|
||||||
|
|
||||||
on_add: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
on_add: ((entity: i53, id: i53, value: any?) -> ())?,
|
||||||
on_change: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
on_change: ((entity: i53, id: i53, value: any) -> ())?,
|
||||||
on_remove: ((entity: i53, id: i53) -> ())?,
|
on_remove: ((entity: i53, id: i53) -> ())?,
|
||||||
|
|
||||||
wildcard_pairs: { [number]: componentrecord },
|
wildcard_pairs: { [number]: componentrecord },
|
||||||
|
@ -140,7 +137,7 @@ type world = {
|
||||||
add: (self: world, id: i53, component: i53) -> (),
|
add: (self: world, id: i53, component: i53) -> (),
|
||||||
set: (self: world, id: i53, component: i53, data: any) -> (),
|
set: (self: world, id: i53, component: i53, data: any) -> (),
|
||||||
cleanup: (self: world) -> (),
|
cleanup: (self: world) -> (),
|
||||||
clear: (self: world, entity: i53) -> (),
|
clear: (self: world, id: i53) -> (),
|
||||||
remove: (self: world, id: i53, component: i53) -> (),
|
remove: (self: world, id: i53, component: i53) -> (),
|
||||||
get: (world, ...i53) -> (),
|
get: (world, ...i53) -> (),
|
||||||
has: (world, ...i53) -> boolean,
|
has: (world, ...i53) -> boolean,
|
||||||
|
@ -169,9 +166,9 @@ export type World = {
|
||||||
|
|
||||||
observable: Map<Id, Map<Id, { Observer }>>,
|
observable: Map<Id, Map<Id, { Observer }>>,
|
||||||
|
|
||||||
added: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T, oldarchetype: Archetype) -> ()) -> () -> (),
|
added: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
||||||
removed: <T>(World, Entity<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
removed: <T>(World, Entity<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
||||||
changed: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T, oldarchetype: Archetype) -> ()) -> () -> (),
|
changed: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
||||||
|
|
||||||
--- Enforce a check on entities to be created within desired range
|
--- Enforce a check on entities to be created within desired range
|
||||||
range: (self: World, range_begin: number, range_end: number?) -> (),
|
range: (self: World, range_begin: number, range_end: number?) -> (),
|
||||||
|
@ -193,9 +190,8 @@ export type World = {
|
||||||
set: <T, a>(self: World, id: Entity<T>, component: Id<a>, data: a) -> (),
|
set: <T, a>(self: World, id: Entity<T>, component: Id<a>, data: a) -> (),
|
||||||
|
|
||||||
cleanup: (self: World) -> (),
|
cleanup: (self: World) -> (),
|
||||||
|
-- Clears an entity from the world
|
||||||
-- Removes all components from the entity
|
clear: <a>(self: World, id: Id<a>) -> (),
|
||||||
clear: (self: World, entity: Entity) -> (),
|
|
||||||
--- Removes a component from the given entity
|
--- Removes a component from the given entity
|
||||||
remove: <T, a>(self: World, id: Entity<T>, component: Id<a>) -> (),
|
remove: <T, a>(self: World, id: Entity<T>, component: Id<a>) -> (),
|
||||||
--- Retrieves the value of up to 4 components. These values may be nil.
|
--- Retrieves the value of up to 4 components. These values may be nil.
|
||||||
|
@ -265,8 +261,8 @@ export type ComponentRecord = {
|
||||||
flags: number,
|
flags: number,
|
||||||
size: number,
|
size: number,
|
||||||
|
|
||||||
on_add: (<T>(entity: Entity, id: Entity<T>, value: T, oldarchetype: Archetype) -> ())?,
|
on_add: (<T>(entity: Entity, id: Entity<T>, value: T?) -> ())?,
|
||||||
on_change: (<T>(entity: Entity, id: Entity<T>, value: T, oldArchetype: Archetype) -> ())?,
|
on_change: (<T>(entity: Entity, id: Entity<T>, value: T) -> ())?,
|
||||||
on_remove: ((entity: Entity, id: Entity) -> ())?,
|
on_remove: ((entity: Entity, id: Entity) -> ())?,
|
||||||
}
|
}
|
||||||
export type ComponentIndex = Map<Id, ComponentRecord>
|
export type ComponentIndex = Map<Id, ComponentRecord>
|
||||||
|
@ -488,7 +484,7 @@ end
|
||||||
|
|
||||||
local ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY = "Entity is outside range"
|
local ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY = "Entity is outside range"
|
||||||
|
|
||||||
local function ENTITY_INDEX_NEW_ID(entity_index: entityindex): i53
|
local function entity_index_new_id(entity_index: entityindex): i53
|
||||||
local dense_array = entity_index.dense_array
|
local dense_array = entity_index.dense_array
|
||||||
local alive_count = entity_index.alive_count
|
local alive_count = entity_index.alive_count
|
||||||
local sparse_array = entity_index.sparse_array
|
local sparse_array = entity_index.sparse_array
|
||||||
|
@ -673,7 +669,7 @@ local function fetch(id: i53, columns_map: { [i53]: Column }, row: number): any
|
||||||
return column[row]
|
return column[row]
|
||||||
end
|
end
|
||||||
|
|
||||||
local function WORLD_GET(world: world, entity: i53,
|
local function world_get(world: world, entity: i53,
|
||||||
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
|
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
|
||||||
local record = entity_index_try_get(world.entity_index, entity)
|
local record = entity_index_try_get(world.entity_index, entity)
|
||||||
if not record then
|
if not record then
|
||||||
|
@ -703,7 +699,7 @@ local function WORLD_GET(world: world, entity: i53,
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function WORLD_HAS(world: world, entity: i53, id: i53): boolean
|
local function world_has_one_inline(world: world, entity: i53, id: i53): boolean
|
||||||
local record = entity_index_try_get(world.entity_index, entity)
|
local record = entity_index_try_get(world.entity_index, entity)
|
||||||
if not record then
|
if not record then
|
||||||
return false
|
return false
|
||||||
|
@ -717,7 +713,7 @@ local function WORLD_HAS(world: world, entity: i53, id: i53): boolean
|
||||||
return archetype.columns_map[id] ~= nil
|
return archetype.columns_map[id] ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function WORLD_TARGET(world: world, entity: i53, relation: i53, index: number?): i53?
|
local function world_target(world: world, entity: i53, relation: i53, index: number?): i53?
|
||||||
local entity_index = world.entity_index
|
local entity_index = world.entity_index
|
||||||
local record = entity_index_try_get(entity_index, entity)
|
local record = entity_index_try_get(entity_index, entity)
|
||||||
if not record then
|
if not record then
|
||||||
|
@ -775,12 +771,15 @@ local function id_record_get(world: World, id: Entity): ComponentRecord?
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function id_record_create(
|
local function id_record_ensure(world: world, id: i53): componentrecord
|
||||||
world: world,
|
local component_index = world.component_index
|
||||||
component_index: Map<i53, componentrecord>,
|
|
||||||
id: i53
|
|
||||||
): componentrecord
|
|
||||||
local entity_index = world.entity_index
|
local entity_index = world.entity_index
|
||||||
|
local idr: componentrecord? = component_index[id]
|
||||||
|
|
||||||
|
if idr then
|
||||||
|
return idr
|
||||||
|
end
|
||||||
|
|
||||||
local flags = ECS_ID_MASK
|
local flags = ECS_ID_MASK
|
||||||
local relation = id
|
local relation = id
|
||||||
local target = 0
|
local target = 0
|
||||||
|
@ -797,31 +796,31 @@ local function id_record_create(
|
||||||
ecs_assert(target and entity_index_is_alive(
|
ecs_assert(target and entity_index_is_alive(
|
||||||
entity_index, target), ECS_INTERNAL_ERROR)
|
entity_index, target), ECS_INTERNAL_ERROR)
|
||||||
|
|
||||||
local cleanup_policy_target = WORLD_TARGET(world, relation, EcsOnDeleteTarget, 0)
|
local cleanup_policy_target = world_target(world, relation, EcsOnDeleteTarget, 0)
|
||||||
|
|
||||||
if cleanup_policy_target == EcsDelete then
|
if cleanup_policy_target == EcsDelete then
|
||||||
has_delete = true
|
has_delete = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if WORLD_HAS(world, relation, EcsExclusive) then
|
if world_has_one_inline(world, relation, EcsExclusive) then
|
||||||
is_exclusive = true
|
is_exclusive = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local cleanup_policy = WORLD_TARGET(world, relation, EcsOnDelete, 0)
|
local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
|
||||||
|
|
||||||
if cleanup_policy == EcsDelete then
|
if cleanup_policy == EcsDelete then
|
||||||
has_delete = true
|
has_delete = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local on_add, on_change, on_remove = WORLD_GET(world,
|
local on_add, on_change, on_remove = world_get(world,
|
||||||
relation, EcsOnAdd, EcsOnChange, EcsOnRemove)
|
relation, EcsOnAdd, EcsOnChange, EcsOnRemove)
|
||||||
|
|
||||||
local is_tag = not WORLD_HAS(world,
|
local is_tag = not world_has_one_inline(world,
|
||||||
relation, EcsComponent)
|
relation, EcsComponent)
|
||||||
|
|
||||||
if is_tag and is_pair then
|
if is_tag and is_pair then
|
||||||
is_tag = not WORLD_HAS(world, target, EcsComponent)
|
is_tag = not world_has_one_inline(world, target, EcsComponent)
|
||||||
end
|
end
|
||||||
|
|
||||||
flags = bit32.bor(
|
flags = bit32.bor(
|
||||||
|
@ -831,7 +830,7 @@ local function id_record_create(
|
||||||
if is_exclusive then ECS_ID_IS_EXCLUSIVE else 0
|
if is_exclusive then ECS_ID_IS_EXCLUSIVE else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
local idr = {
|
idr = {
|
||||||
size = 0,
|
size = 0,
|
||||||
records = {},
|
records = {},
|
||||||
counts = {},
|
counts = {},
|
||||||
|
@ -847,17 +846,6 @@ local function id_record_create(
|
||||||
return idr
|
return idr
|
||||||
end
|
end
|
||||||
|
|
||||||
local function id_record_ensure(world: world, id: i53): componentrecord
|
|
||||||
local component_index = world.component_index
|
|
||||||
local idr: componentrecord? = component_index[id]
|
|
||||||
|
|
||||||
if idr then
|
|
||||||
return idr
|
|
||||||
end
|
|
||||||
|
|
||||||
return id_record_create(world, component_index, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function archetype_append_to_records(
|
local function archetype_append_to_records(
|
||||||
idr: componentrecord,
|
idr: componentrecord,
|
||||||
archetype_id: number,
|
archetype_id: number,
|
||||||
|
@ -927,6 +915,14 @@ local function archetype_create(world: world, id_types: { i53 }, ty, prev: i53?)
|
||||||
|
|
||||||
idr_t.size += 1
|
idr_t.size += 1
|
||||||
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
|
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
|
||||||
|
|
||||||
|
-- Hypothetically this should only capture leaf component records
|
||||||
|
local idr_t_wc_pairs = idr_t.wildcard_pairs
|
||||||
|
if not idr_t_wc_pairs then
|
||||||
|
idr_t_wc_pairs = {} :: {[i53]: componentrecord }
|
||||||
|
idr_t.wildcard_pairs = idr_t_wc_pairs
|
||||||
|
end
|
||||||
|
idr_t_wc_pairs[component_id] = idr
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2053,7 +2049,6 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
local dst_types = ids
|
local dst_types = ids
|
||||||
local to = archetype_ensure(world, dst_types)
|
local to = archetype_ensure(world, dst_types)
|
||||||
new_entity(entity, r, to)
|
new_entity(entity, r, to)
|
||||||
local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
|
|
||||||
for i, id in ids do
|
for i, id in ids do
|
||||||
local value = values[i]
|
local value = values[i]
|
||||||
local cdr = component_index[id]
|
local cdr = component_index[id]
|
||||||
|
@ -2062,11 +2057,11 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
if value then
|
if value then
|
||||||
r.archetype.columns_map[id][r.row] = value
|
r.archetype.columns_map[id][r.row] = value
|
||||||
if on_add then
|
if on_add then
|
||||||
on_add(entity, id, value, ROOT_ARCHETYPE)
|
on_add(entity, id, value :: any)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if on_add then
|
if on_add then
|
||||||
on_add(entity, id, nil, ROOT_ARCHETYPE)
|
on_add(entity, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2108,10 +2103,10 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
local on_change = idr.on_change
|
local on_change = idr.on_change
|
||||||
local hook = if set then on_change else on_add
|
local hook = if set then on_change else on_add
|
||||||
if hook then
|
if hook then
|
||||||
hook(entity, id, value :: any, from)
|
hook(entity, id, value :: any)
|
||||||
end
|
end
|
||||||
elseif on_add then
|
elseif on_add then
|
||||||
on_add(entity, id, nil, from)
|
on_add(entity, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2213,34 +2208,11 @@ local function world_new()
|
||||||
} :: world
|
} :: world
|
||||||
|
|
||||||
|
|
||||||
local function entity_index_new_id(entity_index: entityindex): i53
|
|
||||||
local alive_count = entity_index.alive_count
|
|
||||||
local max_id = entity_index.max_id
|
|
||||||
|
|
||||||
if alive_count < max_id then
|
|
||||||
alive_count += 1
|
|
||||||
entity_index.alive_count = alive_count
|
|
||||||
local id = eindex_dense_array[alive_count]
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
|
|
||||||
local id = max_id + 1
|
|
||||||
local range_end = entity_index.range_end
|
|
||||||
ecs_assert(range_end == nil or id < range_end, ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY)
|
|
||||||
|
|
||||||
entity_index.max_id = id
|
|
||||||
alive_count += 1
|
|
||||||
entity_index.alive_count = alive_count
|
|
||||||
eindex_dense_array[alive_count] = id
|
|
||||||
eindex_sparse_array[id] = { dense = alive_count } :: record
|
|
||||||
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
|
|
||||||
local ROOT_ARCHETYPE = archetype_create(world, {}, "")
|
local ROOT_ARCHETYPE = archetype_create(world, {}, "")
|
||||||
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
||||||
|
|
||||||
local function entity_index_try_get_any(entity: i53): record?
|
local function inner_entity_index_try_get_any(entity: i53): record?
|
||||||
local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)]
|
local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)]
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
@ -2302,6 +2274,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function inner_entity_move(
|
local function inner_entity_move(
|
||||||
|
entity_index: entityindex,
|
||||||
entity: i53,
|
entity: i53,
|
||||||
record: record,
|
record: record,
|
||||||
to: archetype
|
to: archetype
|
||||||
|
@ -2314,8 +2287,8 @@ local function world_new()
|
||||||
record.row = dst_row
|
record.row = dst_row
|
||||||
end
|
end
|
||||||
|
|
||||||
-- local function entity_index_try_get(entity: number): Record?
|
-- local function inner_entity_index_try_get(entity: number): Record?
|
||||||
-- local r = entity_index_try_get_any(entity)
|
-- local r = inner_entity_index_try_get_any(entity)
|
||||||
-- if r then
|
-- if r then
|
||||||
-- local r_dense = r.dense
|
-- local r_dense = r.dense
|
||||||
-- if r_dense > entity_index.alive_count then
|
-- if r_dense > entity_index.alive_count then
|
||||||
|
@ -2328,7 +2301,7 @@ local function world_new()
|
||||||
-- return r
|
-- return r
|
||||||
-- end
|
-- end
|
||||||
|
|
||||||
local function entity_index_try_get_unsafe(entity: i53): record?
|
local function inner_entity_index_try_get_unsafe(entity: i53): record?
|
||||||
local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)]
|
local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)]
|
||||||
if r then
|
if r then
|
||||||
local r_dense = r.dense
|
local r_dense = r.dense
|
||||||
|
@ -2358,8 +2331,8 @@ local function world_new()
|
||||||
return to
|
return to
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_set(world: world, entity: i53, id: i53, data): ()
|
local function inner_world_set(world: world, entity: i53, id: i53, data): ()
|
||||||
local record = entity_index_try_get_unsafe(entity)
|
local record = inner_entity_index_try_get_unsafe(entity)
|
||||||
if not record then
|
if not record then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -2375,7 +2348,7 @@ local function world_new()
|
||||||
-- and just set the data directly.
|
-- and just set the data directly.
|
||||||
local on_change = idr.on_change
|
local on_change = idr.on_change
|
||||||
if on_change then
|
if on_change then
|
||||||
on_change(entity, id, data, src)
|
on_change(entity, id, data)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local to: archetype
|
local to: archetype
|
||||||
|
@ -2445,7 +2418,7 @@ local function world_new()
|
||||||
|
|
||||||
if from then
|
if from then
|
||||||
-- If there was a previous archetype, then the entity needs to move the archetype
|
-- If there was a previous archetype, then the entity needs to move the archetype
|
||||||
inner_entity_move(entity, record, to)
|
inner_entity_move(entity_index, entity, record, to)
|
||||||
else
|
else
|
||||||
new_entity(entity, record, to)
|
new_entity(entity, record, to)
|
||||||
end
|
end
|
||||||
|
@ -2455,17 +2428,18 @@ local function world_new()
|
||||||
|
|
||||||
local on_add = idr.on_add
|
local on_add = idr.on_add
|
||||||
if on_add then
|
if on_add then
|
||||||
on_add(entity, id, data, src)
|
on_add(entity, id, data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_add(
|
local function inner_world_add(
|
||||||
world: world,
|
world: world,
|
||||||
entity: i53,
|
entity: i53,
|
||||||
id: i53
|
id: i53
|
||||||
): ()
|
): ()
|
||||||
local record = entity_index_try_get_unsafe(entity :: number)
|
local entity_index = world.entity_index
|
||||||
|
local record = inner_entity_index_try_get_unsafe(entity :: number)
|
||||||
if not record then
|
if not record then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -2543,7 +2517,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
if from then
|
if from then
|
||||||
inner_entity_move(entity, record, to)
|
inner_entity_move(entity_index, entity, record, to)
|
||||||
else
|
else
|
||||||
if #to.types > 0 then
|
if #to.types > 0 then
|
||||||
new_entity(entity, record, to)
|
new_entity(entity, record, to)
|
||||||
|
@ -2553,13 +2527,13 @@ local function world_new()
|
||||||
local on_add = idr.on_add
|
local on_add = idr.on_add
|
||||||
|
|
||||||
if on_add then
|
if on_add then
|
||||||
on_add(entity, id, nil, src)
|
on_add(entity, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_get(world: world, entity: i53,
|
local function inner_world_get(world: world, entity: i53,
|
||||||
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
|
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
|
||||||
local record = entity_index_try_get_unsafe(entity)
|
local record = inner_entity_index_try_get_unsafe(entity)
|
||||||
if not record then
|
if not record then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
@ -2587,7 +2561,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
type Listener<T> = (e: i53, id: i53, value: T, oldarchetype: archetype) -> ()
|
type Listener<T> = (e: i53, id: i53, value: T?) -> ()
|
||||||
|
|
||||||
world.added = function<T>(_: world, component: i53, fn: Listener<T>)
|
world.added = function<T>(_: world, component: i53, fn: Listener<T>)
|
||||||
local listeners = signals.added[component]
|
local listeners = signals.added[component]
|
||||||
|
@ -2595,12 +2569,12 @@ local function world_new()
|
||||||
listeners = {}
|
listeners = {}
|
||||||
signals.added[component] = listeners
|
signals.added[component] = listeners
|
||||||
|
|
||||||
local function on_add(entity, id, value, oldarchetype)
|
local function on_add(entity, id, value)
|
||||||
for _, listener in listeners :: { Listener<T> } do
|
for _, listener in listeners :: { Listener<T> } do
|
||||||
listener(entity, id, value, oldarchetype)
|
listener(entity, id, value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local existing_hook = world_get(world, component, EcsOnAdd) :: Listener<T>
|
local existing_hook = inner_world_get(world, component, EcsOnAdd) :: Listener<T>
|
||||||
if existing_hook then
|
if existing_hook then
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
@ -2618,7 +2592,7 @@ local function world_new()
|
||||||
idr.on_add = on_add
|
idr.on_add = on_add
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
world_set(world, component, EcsOnAdd, on_add)
|
inner_world_set(world, component, EcsOnAdd, on_add)
|
||||||
end
|
end
|
||||||
table.insert(listeners, fn)
|
table.insert(listeners, fn)
|
||||||
return function()
|
return function()
|
||||||
|
@ -2638,12 +2612,12 @@ local function world_new()
|
||||||
if not listeners then
|
if not listeners then
|
||||||
listeners = {}
|
listeners = {}
|
||||||
signals.changed[component] = listeners
|
signals.changed[component] = listeners
|
||||||
local function on_change(entity, id, value, oldarchetype)
|
local function on_change(entity, id, value: any)
|
||||||
for _, listener in listeners :: { Listener<T> } do
|
for _, listener in listeners :: { Listener<T> } do
|
||||||
listener(entity, id, value, oldarchetype)
|
listener(entity, id, value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local existing_hook = world_get(world, component, EcsOnChange) :: Listener<T>
|
local existing_hook = inner_world_get(world, component, EcsOnChange) :: Listener<T>
|
||||||
if existing_hook then
|
if existing_hook then
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
@ -2663,7 +2637,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
world_set(world, component, EcsOnChange, on_change)
|
inner_world_set(world, component, EcsOnChange, on_change)
|
||||||
end
|
end
|
||||||
table.insert(listeners, fn)
|
table.insert(listeners, fn)
|
||||||
return function()
|
return function()
|
||||||
|
@ -2680,12 +2654,12 @@ local function world_new()
|
||||||
listeners = {}
|
listeners = {}
|
||||||
signals.removed[component] = listeners
|
signals.removed[component] = listeners
|
||||||
local function on_remove(entity, id)
|
local function on_remove(entity, id)
|
||||||
for _, listener in listeners :: { (...any) -> () } do
|
for _, listener in listeners :: { Listener<T> } do
|
||||||
listener(entity, id)
|
listener(entity, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local existing_hook = world_get(world, component, EcsOnRemove) :: Listener<T>
|
local existing_hook = inner_world_get(world, component, EcsOnRemove) :: Listener<T>
|
||||||
if existing_hook then
|
if existing_hook then
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
@ -2705,7 +2679,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
world_set(world, component, EcsOnRemove, on_remove)
|
inner_world_set(world, component, EcsOnRemove, on_remove)
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(listeners, fn)
|
table.insert(listeners, fn)
|
||||||
|
@ -2718,10 +2692,10 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_has(world: World, entity: i53,
|
local function inner_world_has(world: World, entity: i53,
|
||||||
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): boolean
|
a: i53, b: i53?, c: i53?, d: i53?, e: i53?): boolean
|
||||||
|
|
||||||
local record = entity_index_try_get_unsafe(entity)
|
local record = inner_entity_index_try_get_unsafe(entity)
|
||||||
if not record then
|
if not record then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
@ -2740,8 +2714,8 @@ local function world_new()
|
||||||
(e == nil or error("args exceeded"))
|
(e == nil or error("args exceeded"))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_target(world: world, entity: i53, relation: i53, index: number?): i53?
|
local function inner_world_target(world: world, entity: i53, relation: i53, index: number?): i53?
|
||||||
local record = entity_index_try_get_unsafe(entity)
|
local record = inner_entity_index_try_get_unsafe(entity)
|
||||||
if not record then
|
if not record then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
@ -2780,11 +2754,11 @@ local function world_new()
|
||||||
ECS_PAIR_SECOND(nth))
|
ECS_PAIR_SECOND(nth))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_parent(world: world, entity: i53): i53?
|
local function inner_world_parent(world: world, entity: i53): i53?
|
||||||
return world_target(world, entity, EcsChildOf, 0)
|
return inner_world_target(world, entity, EcsChildOf, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_entity(world: world, entity: i53?): i53
|
local function inner_world_entity(world: world, entity: i53?): i53
|
||||||
if entity then
|
if entity then
|
||||||
local index = ECS_ID(entity)
|
local index = ECS_ID(entity)
|
||||||
local alive_count = entity_index.alive_count
|
local alive_count = entity_index.alive_count
|
||||||
|
@ -2796,7 +2770,7 @@ local function world_new()
|
||||||
r.dense = index
|
r.dense = index
|
||||||
dense = index
|
dense = index
|
||||||
local e_swap = eindex_dense_array[dense]
|
local e_swap = eindex_dense_array[dense]
|
||||||
local r_swap = entity_index_try_get_any(e_swap) :: record
|
local r_swap = inner_entity_index_try_get_any(e_swap) :: record
|
||||||
|
|
||||||
r_swap.dense = dense
|
r_swap.dense = dense
|
||||||
alive_count += 1
|
alive_count += 1
|
||||||
|
@ -2812,7 +2786,7 @@ local function world_new()
|
||||||
if any ~= entity then
|
if any ~= entity then
|
||||||
if alive_count <= dense then
|
if alive_count <= dense then
|
||||||
local e_swap = eindex_dense_array[dense]
|
local e_swap = eindex_dense_array[dense]
|
||||||
local r_swap = entity_index_try_get_any(e_swap) :: record
|
local r_swap = inner_entity_index_try_get_any(e_swap) :: record
|
||||||
|
|
||||||
r_swap.dense = dense
|
r_swap.dense = dense
|
||||||
alive_count += 1
|
alive_count += 1
|
||||||
|
@ -2854,8 +2828,8 @@ local function world_new()
|
||||||
return entity_index_new_id(entity_index)
|
return entity_index_new_id(entity_index)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_remove(world: world, entity: i53, id: i53)
|
local function inner_world_remove(world: world, entity: i53, id: i53)
|
||||||
local record = entity_index_try_get_unsafe(entity)
|
local record = inner_entity_index_try_get_unsafe(entity)
|
||||||
if not record then
|
if not record then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -2875,12 +2849,99 @@ local function world_new()
|
||||||
|
|
||||||
local to = archetype_traverse_remove(world, id, record.archetype)
|
local to = archetype_traverse_remove(world, id, record.archetype)
|
||||||
|
|
||||||
inner_entity_move(entity, record, to)
|
inner_entity_move(entity_index, entity, record, to)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_delete(world: world, entity: i53)
|
local function inner_world_clear(world: world, entity: i53)
|
||||||
local record = entity_index_try_get_unsafe(entity)
|
local tgt = ECS_PAIR(EcsWildcard, entity)
|
||||||
|
local idr_t = component_index[tgt]
|
||||||
|
local idr = component_index[entity]
|
||||||
|
local rel = ECS_PAIR(entity, EcsWildcard)
|
||||||
|
local idr_r = component_index[rel]
|
||||||
|
|
||||||
|
if idr then
|
||||||
|
local count = 0
|
||||||
|
local queue = {}
|
||||||
|
for archetype_id in idr.records do
|
||||||
|
local idr_archetype = archetypes[archetype_id]
|
||||||
|
local entities = idr_archetype.entities
|
||||||
|
local n = #entities
|
||||||
|
table.move(entities, 1, n, count + 1, queue)
|
||||||
|
count += n
|
||||||
|
end
|
||||||
|
for _, e in queue do
|
||||||
|
inner_world_remove(world, e, entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if idr_t then
|
||||||
|
local archetype_ids = idr_t.records
|
||||||
|
for archetype_id in archetype_ids do
|
||||||
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
|
local idr_t_types = idr_t_archetype.types
|
||||||
|
local entities = idr_t_archetype.entities
|
||||||
|
|
||||||
|
local node = idr_t_archetype
|
||||||
|
|
||||||
|
for _, id in idr_t_types do
|
||||||
|
if not ECS_IS_PAIR(id) then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
local object = entity_index_get_alive(
|
||||||
|
entity_index, ECS_PAIR_SECOND(id))
|
||||||
|
if object ~= entity then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
node = archetype_traverse_remove(world, id, node)
|
||||||
|
local on_remove = component_index[id].on_remove
|
||||||
|
if on_remove then
|
||||||
|
for _, entity in entities do
|
||||||
|
on_remove(entity, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = #entities, 1, -1 do
|
||||||
|
local e = entities[i]
|
||||||
|
local r = inner_entity_index_try_get_unsafe(e) :: record
|
||||||
|
inner_entity_move(entity_index, e, r, node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if idr_r then
|
||||||
|
local archetype_ids = idr_r.records
|
||||||
|
local records = idr_r.records
|
||||||
|
local counts = idr_r.counts
|
||||||
|
for archetype_id in archetype_ids do
|
||||||
|
local idr_r_archetype = archetypes[archetype_id]
|
||||||
|
local node = idr_r_archetype
|
||||||
|
local entities = idr_r_archetype.entities
|
||||||
|
local tr = records[archetype_id]
|
||||||
|
local tr_count = counts[archetype_id]
|
||||||
|
local types = idr_r_archetype.types
|
||||||
|
for i = tr, tr + tr_count - 1 do
|
||||||
|
local id = types[i]
|
||||||
|
node = archetype_traverse_remove(world, id, idr_r_archetype)
|
||||||
|
local on_remove = component_index[id].on_remove
|
||||||
|
if on_remove then
|
||||||
|
for _, entity in entities do
|
||||||
|
on_remove(entity, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i = #entities, 1, -1 do
|
||||||
|
local e = entities[i]
|
||||||
|
local r = inner_entity_index_try_get_unsafe(e) :: record
|
||||||
|
inner_entity_move(entity_index, e, r, node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function inner_world_delete(world: world, entity: i53)
|
||||||
|
local record = inner_entity_index_try_get_unsafe(entity)
|
||||||
if not record then
|
if not record then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -2916,7 +2977,7 @@ local function world_new()
|
||||||
local entities = idr_archetype.entities
|
local entities = idr_archetype.entities
|
||||||
local n = #entities
|
local n = #entities
|
||||||
for i = n, 1, -1 do
|
for i = n, 1, -1 do
|
||||||
world_delete(world, entities[i])
|
inner_world_delete(world, entities[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
archetype_destroy(world, idr_archetype)
|
archetype_destroy(world, idr_archetype)
|
||||||
|
@ -2939,7 +3000,7 @@ local function world_new()
|
||||||
-- this is hypothetically not that expensive of an operation anyways
|
-- this is hypothetically not that expensive of an operation anyways
|
||||||
to = archetype_traverse_remove(world, entity, from)
|
to = archetype_traverse_remove(world, entity, from)
|
||||||
end
|
end
|
||||||
inner_entity_move(e, r, to)
|
inner_entity_move(entity_index, e, r, to)
|
||||||
end
|
end
|
||||||
|
|
||||||
archetype_destroy(world, idr_archetype)
|
archetype_destroy(world, idr_archetype)
|
||||||
|
@ -2961,51 +3022,55 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if idr_t then
|
if idr_t then
|
||||||
local archetype_ids = idr_t.records
|
for id, cr in idr_t.wildcard_pairs do
|
||||||
for archetype_id in archetype_ids do
|
local flags = cr.flags
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
|
||||||
local idr_t_types = idr_t_archetype.types
|
|
||||||
local entities = idr_t_archetype.entities
|
|
||||||
|
|
||||||
for _, id in idr_t_types do
|
|
||||||
if not ECS_IS_PAIR(id) then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
local object = entity_index_get_alive(
|
|
||||||
entity_index, ECS_PAIR_SECOND(id))
|
|
||||||
if object ~= entity then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
local id_record = component_index[id]
|
|
||||||
local flags = id_record.flags
|
|
||||||
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
|
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
|
||||||
|
local on_remove = cr.on_remove
|
||||||
if flags_delete_mask then
|
if flags_delete_mask then
|
||||||
|
for archetype_id in cr.records do
|
||||||
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
|
local entities = idr_t_archetype.entities
|
||||||
for i = #entities, 1, -1 do
|
for i = #entities, 1, -1 do
|
||||||
local child = entities[i]
|
local child = entities[i]
|
||||||
world_delete(world, child)
|
inner_world_delete(world, child)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
break
|
|
||||||
else
|
else
|
||||||
local on_remove = id_record.on_remove
|
for archetype_id in cr.records do
|
||||||
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
|
local entities = idr_t_archetype.entities
|
||||||
|
-- archetype_traverse_remove is not idempotent meaning
|
||||||
|
-- this access is actually unsafe because it can
|
||||||
|
-- incorrectly cache an edge despite a node of the
|
||||||
|
-- component id on the archetype does not exist. This
|
||||||
|
-- requires careful testing to ensure correct values are
|
||||||
|
-- being passed to the arguments.
|
||||||
|
local to = archetype_traverse_remove(world, id, idr_t_archetype)
|
||||||
|
|
||||||
for i = #entities, 1, -1 do
|
for i = #entities, 1, -1 do
|
||||||
local child = entities[i]
|
local e = entities[i]
|
||||||
|
local r = eindex_sparse_array[ECS_ID(e :: number)]
|
||||||
if on_remove then
|
if on_remove then
|
||||||
on_remove(child, id)
|
on_remove(e, id)
|
||||||
|
|
||||||
|
local from = r.archetype
|
||||||
|
if from ~= idr_t_archetype then
|
||||||
|
-- unfortunately the on_remove hook allows a window where `e` can have changed archetype
|
||||||
|
-- this is hypothetically not that expensive of an operation anyways
|
||||||
|
to = archetype_traverse_remove(world, id, from)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local r = entity_index_try_get_unsafe(child) :: record
|
inner_entity_move(entity_index, e, r, to)
|
||||||
local to = archetype_traverse_remove(world, id, r.archetype)
|
|
||||||
inner_entity_move(child, r, to)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for archetype_id in archetype_ids do
|
for archetype_id in cr.records do
|
||||||
archetype_destroy(world, archetypes[archetype_id])
|
archetype_destroy(world, archetypes[archetype_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if idr_r then
|
if idr_r then
|
||||||
local archetype_ids = idr_r.records
|
local archetype_ids = idr_r.records
|
||||||
|
@ -3017,7 +3082,7 @@ local function world_new()
|
||||||
local entities = idr_r_archetype.entities
|
local entities = idr_r_archetype.entities
|
||||||
local n = #entities
|
local n = #entities
|
||||||
for i = n, 1, -1 do
|
for i = n, 1, -1 do
|
||||||
world_delete(world, entities[i])
|
inner_world_delete(world, entities[i])
|
||||||
end
|
end
|
||||||
archetype_destroy(world, idr_r_archetype)
|
archetype_destroy(world, idr_r_archetype)
|
||||||
end
|
end
|
||||||
|
@ -3044,8 +3109,8 @@ local function world_new()
|
||||||
|
|
||||||
for i = #entities, 1, -1 do
|
for i = #entities, 1, -1 do
|
||||||
local e = entities[i]
|
local e = entities[i]
|
||||||
local r = entity_index_try_get_unsafe(e) :: record
|
local r = inner_entity_index_try_get_unsafe(e) :: record
|
||||||
inner_entity_move(e, r, node)
|
inner_entity_move(entity_index, e, r, node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -3055,13 +3120,12 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
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 = eindex_dense_array[i_swap]
|
local e_swap = eindex_dense_array[i_swap]
|
||||||
local r_swap = entity_index_try_get_any(e_swap) :: record
|
local r_swap = inner_entity_index_try_get_any(e_swap) :: 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
|
||||||
|
@ -3071,34 +3135,15 @@ local function world_new()
|
||||||
eindex_dense_array[i_swap] = ECS_GENERATION_INC(entity)
|
eindex_dense_array[i_swap] = ECS_GENERATION_INC(entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_clear(world: world, entity: i53)
|
local function inner_world_exists(world: world, entity: i53): boolean
|
||||||
local record = entity_index_try_get_unsafe(entity)
|
return inner_entity_index_try_get_any(entity) ~= nil
|
||||||
if not record then
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetype = record.archetype
|
local function inner_world_contains(world: world, entity: i53): boolean
|
||||||
for _, id in archetype.types do
|
|
||||||
local idr = component_index[id]
|
|
||||||
local on_remove = idr.on_remove
|
|
||||||
if on_remove then
|
|
||||||
on_remove(entity, id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
archetype_delete(world, record.archetype, record.row)
|
|
||||||
record.archetype = nil :: any
|
|
||||||
record.row = nil :: any
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_exists(world: world, entity: i53): boolean
|
|
||||||
return entity_index_try_get_any(entity) ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_contains(world: world, entity: i53): boolean
|
|
||||||
return entity_index_is_alive(entity_index, entity)
|
return entity_index_is_alive(entity_index, entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_cleanup(world: world)
|
local function inner_world_cleanup(world: world)
|
||||||
for _, archetype in archetypes do
|
for _, archetype in archetypes do
|
||||||
if #archetype.entities == 0 then
|
if #archetype.entities == 0 then
|
||||||
archetype_destroy(world, archetype)
|
archetype_destroy(world, archetype)
|
||||||
|
@ -3128,27 +3173,26 @@ local function world_new()
|
||||||
error("Too many components, consider using world:entity() instead to create components.")
|
error("Too many components, consider using world:entity() instead to create components.")
|
||||||
end
|
end
|
||||||
world.max_component_id = max_component_id
|
world.max_component_id = max_component_id
|
||||||
world_add(world, max_component_id, EcsComponent)
|
inner_world_add(world, max_component_id, EcsComponent)
|
||||||
|
|
||||||
return max_component_id
|
return max_component_id
|
||||||
end
|
end
|
||||||
|
|
||||||
world.entity = world_entity
|
world.entity = inner_world_entity
|
||||||
world.query = world_query :: any
|
world.query = world_query :: any
|
||||||
world.remove = world_remove
|
world.remove = inner_world_remove
|
||||||
world.clear = world_clear
|
world.clear = inner_world_clear
|
||||||
-- world.purge = world_purge
|
world.delete = inner_world_delete
|
||||||
world.delete = world_delete
|
|
||||||
world.component = world_component
|
world.component = world_component
|
||||||
world.add = world_add
|
world.add = inner_world_add
|
||||||
world.set = world_set
|
world.set = inner_world_set
|
||||||
world.get = world_get :: any
|
world.get = inner_world_get :: any
|
||||||
world.has = world_has :: any
|
world.has = inner_world_has :: any
|
||||||
world.target = world_target
|
world.target = inner_world_target
|
||||||
world.parent = world_parent
|
world.parent = inner_world_parent
|
||||||
world.contains = world_contains
|
world.contains = inner_world_contains
|
||||||
world.exists = world_exists
|
world.exists = inner_world_exists
|
||||||
world.cleanup = world_cleanup
|
world.cleanup = inner_world_cleanup
|
||||||
world.each = world_each
|
world.each = world_each
|
||||||
world.children = world_children
|
world.children = world_children
|
||||||
world.range = world_range
|
world.range = world_range
|
||||||
|
@ -3158,36 +3202,36 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, max_component_id do
|
for i = 1, max_component_id do
|
||||||
world_add(world, i, EcsComponent)
|
inner_world_add(world, i, EcsComponent)
|
||||||
end
|
end
|
||||||
|
|
||||||
world_add(world, EcsName, EcsComponent)
|
inner_world_add(world, EcsName, EcsComponent)
|
||||||
world_add(world, EcsOnChange, EcsComponent)
|
inner_world_add(world, EcsOnChange, EcsComponent)
|
||||||
world_add(world, EcsOnAdd, EcsComponent)
|
inner_world_add(world, EcsOnAdd, EcsComponent)
|
||||||
world_add(world, EcsOnRemove, EcsComponent)
|
inner_world_add(world, EcsOnRemove, EcsComponent)
|
||||||
world_add(world, EcsWildcard, EcsComponent)
|
inner_world_add(world, EcsWildcard, EcsComponent)
|
||||||
world_add(world, EcsRest, EcsComponent)
|
inner_world_add(world, EcsRest, EcsComponent)
|
||||||
|
|
||||||
world_set(world, EcsOnAdd, EcsName, "jecs.OnAdd")
|
inner_world_set(world, EcsOnAdd, EcsName, "jecs.OnAdd")
|
||||||
world_set(world, EcsOnRemove, EcsName, "jecs.OnRemove")
|
inner_world_set(world, EcsOnRemove, EcsName, "jecs.OnRemove")
|
||||||
world_set(world, EcsOnChange, EcsName, "jecs.OnChange")
|
inner_world_set(world, EcsOnChange, EcsName, "jecs.OnChange")
|
||||||
world_set(world, EcsWildcard, EcsName, "jecs.Wildcard")
|
inner_world_set(world, EcsWildcard, EcsName, "jecs.Wildcard")
|
||||||
world_set(world, EcsChildOf, EcsName, "jecs.ChildOf")
|
inner_world_set(world, EcsChildOf, EcsName, "jecs.ChildOf")
|
||||||
world_set(world, EcsComponent, EcsName, "jecs.Component")
|
inner_world_set(world, EcsComponent, EcsName, "jecs.Component")
|
||||||
|
|
||||||
world_set(world, EcsOnDelete, EcsName, "jecs.OnDelete")
|
inner_world_set(world, EcsOnDelete, EcsName, "jecs.OnDelete")
|
||||||
world_set(world, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget")
|
inner_world_set(world, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget")
|
||||||
|
|
||||||
world_set(world, EcsDelete, EcsName, "jecs.Delete")
|
inner_world_set(world, EcsDelete, EcsName, "jecs.Delete")
|
||||||
world_set(world, EcsRemove, EcsName, "jecs.Remove")
|
inner_world_set(world, EcsRemove, EcsName, "jecs.Remove")
|
||||||
world_set(world, EcsName, EcsName, "jecs.Name")
|
inner_world_set(world, EcsName, EcsName, "jecs.Name")
|
||||||
world_set(world, EcsRest, EcsRest, "jecs.Rest")
|
inner_world_set(world, EcsRest, EcsRest, "jecs.Rest")
|
||||||
|
|
||||||
world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
|
inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
|
||||||
world_add(world, EcsChildOf, EcsExclusive)
|
inner_world_add(world, EcsChildOf, EcsExclusive)
|
||||||
|
|
||||||
world_add(world, EcsOnDelete, EcsExclusive)
|
inner_world_add(world, EcsOnDelete, EcsExclusive)
|
||||||
world_add(world, EcsOnDeleteTarget, EcsExclusive)
|
inner_world_add(world, EcsOnDeleteTarget, EcsExclusive)
|
||||||
|
|
||||||
for i = EcsRest + 1, ecs_max_tag_id do
|
for i = EcsRest + 1, ecs_max_tag_id do
|
||||||
entity_index_new_id(entity_index)
|
entity_index_new_id(entity_index)
|
||||||
|
@ -3196,9 +3240,9 @@ local function world_new()
|
||||||
for i, bundle in ecs_metadata do
|
for i, bundle in ecs_metadata do
|
||||||
for ty, value in bundle do
|
for ty, value in bundle do
|
||||||
if value == NULL then
|
if value == NULL then
|
||||||
world_add(world, i, ty)
|
inner_world_add(world, i, ty)
|
||||||
else
|
else
|
||||||
world_set(world, i, ty, value)
|
inner_world_set(world, i, ty, value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3229,7 +3273,7 @@ local function ecs_is_tag(world: world, entity: i53): boolean
|
||||||
if idr then
|
if idr then
|
||||||
return bit32.btest(idr.flags, ECS_ID_IS_TAG)
|
return bit32.btest(idr.flags, ECS_ID_IS_TAG)
|
||||||
end
|
end
|
||||||
return not WORLD_HAS(world, entity, EcsComponent)
|
return not world_has_one_inline(world, entity, EcsComponent)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ecs_entity_record(world: world, entity: i53)
|
local function ecs_entity_record(world: world, entity: i53)
|
||||||
|
@ -3294,7 +3338,7 @@ return {
|
||||||
entity_index_try_get_fast = entity_index_try_get_fast :: (EntityIndex, Entity) -> Record?,
|
entity_index_try_get_fast = entity_index_try_get_fast :: (EntityIndex, Entity) -> Record?,
|
||||||
entity_index_try_get_any = entity_index_try_get_any :: (EntityIndex, Entity) -> Record,
|
entity_index_try_get_any = entity_index_try_get_any :: (EntityIndex, Entity) -> Record,
|
||||||
entity_index_is_alive = entity_index_is_alive :: (EntityIndex, Entity) -> boolean,
|
entity_index_is_alive = entity_index_is_alive :: (EntityIndex, Entity) -> boolean,
|
||||||
entity_index_new_id = ENTITY_INDEX_NEW_ID :: (EntityIndex) -> Entity,
|
entity_index_new_id = entity_index_new_id :: (EntityIndex) -> Entity,
|
||||||
|
|
||||||
Query = Query,
|
Query = Query,
|
||||||
|
|
||||||
|
|
305
test/tests.luau
305
test/tests.luau
|
@ -24,6 +24,39 @@ type Id<T=unknown> = jecs.Id<T>
|
||||||
local entity_visualiser = require("@tools/entity_visualiser")
|
local entity_visualiser = require("@tools/entity_visualiser")
|
||||||
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()
|
TEST("Ensure archetype edges get cleaned", function()
|
||||||
local A = jecs.component()
|
local A = jecs.component()
|
||||||
local B = jecs.component()
|
local B = jecs.component()
|
||||||
|
@ -58,7 +91,6 @@ TEST("Ensure archetype edges get cleaned", function()
|
||||||
CHECK(false)
|
CHECK(false)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("repeated entity cached query", function()
|
TEST("repeated entity cached query", function()
|
||||||
local pair = jecs.pair
|
local pair = jecs.pair
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
@ -559,146 +591,100 @@ TEST("world:children()", function()
|
||||||
jecs.ECS_META_RESET()
|
jecs.ECS_META_RESET()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- TEST("world:purge()", function()
|
TEST("world:clear()", function()
|
||||||
-- do CASE "should remove all instances of specified component"
|
do CASE "should remove its components"
|
||||||
-- local world = jecs.world()
|
|
||||||
-- local A = world:component()
|
|
||||||
-- local B = world:component()
|
|
||||||
|
|
||||||
-- local e = world:entity()
|
|
||||||
-- local e1 = world:entity()
|
|
||||||
-- local _e2 = world:entity()
|
|
||||||
|
|
||||||
-- world:set(e, A, true)
|
|
||||||
-- world:set(e, B, true)
|
|
||||||
|
|
||||||
-- world:set(e1, A, true)
|
|
||||||
-- world:set(e1, B, true)
|
|
||||||
|
|
||||||
-- CHECK(world:get(e, A))
|
|
||||||
-- CHECK(world:get(e, B))
|
|
||||||
|
|
||||||
-- world:purge(A)
|
|
||||||
-- CHECK(world:get(e, A) == nil)
|
|
||||||
-- CHECK(world:get(e, B))
|
|
||||||
-- CHECK(world:get(e1, A) == nil)
|
|
||||||
-- CHECK(world:get(e1, B))
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- do CASE "remove purged component from entities"
|
|
||||||
-- local world = jecs.world()
|
|
||||||
-- local A = world:component()
|
|
||||||
-- local B = world:component()
|
|
||||||
-- local C = world:component()
|
|
||||||
|
|
||||||
-- do
|
|
||||||
-- local id1 = world:entity()
|
|
||||||
-- local id2 = world:entity()
|
|
||||||
-- local id3 = world:entity()
|
|
||||||
|
|
||||||
-- world:set(id1, A, true)
|
|
||||||
|
|
||||||
-- world:set(id2, A, true)
|
|
||||||
-- world:set(id2, B, true)
|
|
||||||
|
|
||||||
-- world:set(id3, A, true)
|
|
||||||
-- world:set(id3, B, true)
|
|
||||||
-- world:set(id3, C, true)
|
|
||||||
|
|
||||||
-- world:purge(A)
|
|
||||||
|
|
||||||
-- CHECK(not world:has(id1, A))
|
|
||||||
-- CHECK(not world:has(id2, A))
|
|
||||||
-- CHECK(not world:has(id3, A))
|
|
||||||
|
|
||||||
-- CHECK(world:has(id2, B))
|
|
||||||
-- CHECK(world:has(id3, B, C))
|
|
||||||
|
|
||||||
-- world:purge(C)
|
|
||||||
|
|
||||||
-- CHECK(world:has(id2, B))
|
|
||||||
-- CHECK(world:has(id3, B))
|
|
||||||
|
|
||||||
-- CHECK(world:contains(A))
|
|
||||||
-- CHECK(world:contains(C))
|
|
||||||
-- CHECK(world:has(A, jecs.Component))
|
|
||||||
-- CHECK(world:has(B, jecs.Component))
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- do
|
|
||||||
-- local id1 = world:entity()
|
|
||||||
-- local id2 = world:entity()
|
|
||||||
-- local id3 = world:entity()
|
|
||||||
|
|
||||||
-- local tgt = world:entity()
|
|
||||||
|
|
||||||
-- world:add(id1, pair(A, tgt))
|
|
||||||
-- world:add(id1, pair(B, tgt))
|
|
||||||
-- world:add(id1, pair(C, tgt))
|
|
||||||
|
|
||||||
-- world:add(id2, pair(A, tgt))
|
|
||||||
-- world:add(id2, pair(B, tgt))
|
|
||||||
-- world:add(id2, pair(C, tgt))
|
|
||||||
|
|
||||||
-- world:add(id3, pair(A, tgt))
|
|
||||||
-- world:add(id3, pair(B, tgt))
|
|
||||||
-- world:add(id3, pair(C, tgt))
|
|
||||||
|
|
||||||
-- world:purge(B)
|
|
||||||
-- CHECK(world:has(id1, pair(A, tgt), pair(C, tgt)))
|
|
||||||
-- CHECK(not world:has(id1, pair(B, tgt)))
|
|
||||||
-- CHECK(world:has(id2, pair(A, tgt), pair(C, tgt)))
|
|
||||||
-- CHECK(not world:has(id1, pair(B, tgt)))
|
|
||||||
-- CHECK(world:has(id3, pair(A, tgt), pair(C, tgt)))
|
|
||||||
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- end
|
|
||||||
-- end)
|
|
||||||
|
|
||||||
TEST("world:clear", function()
|
|
||||||
do CASE "remove all components on entity"
|
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
|
local e1 = world:entity()
|
||||||
|
local _e2 = world:entity()
|
||||||
|
|
||||||
world:set(e, A, true)
|
world:set(e, A, true)
|
||||||
world:set(e, B, true)
|
world:set(e, B, true)
|
||||||
|
|
||||||
world:clear(e)
|
world:set(e1, A, true)
|
||||||
|
world:set(e1, B, true)
|
||||||
|
|
||||||
CHECK(world:contains(e))
|
CHECK(world:get(e, A))
|
||||||
CHECK(not world:has(e, A))
|
CHECK(world:get(e, B))
|
||||||
CHECK(not world:has(e, B))
|
|
||||||
print(jecs.record(world, e).archetype == nil::any)
|
world:clear(A)
|
||||||
|
CHECK(world:get(e, A) == nil)
|
||||||
|
CHECK(world:get(e, B))
|
||||||
|
CHECK(world:get(e1, A) == nil)
|
||||||
|
CHECK(world:get(e1, B))
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "should invoke hooks"
|
do CASE "remove cleared ID from entities"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local called = 0
|
|
||||||
world:set(A, jecs.OnRemove, function()
|
|
||||||
called += 1
|
|
||||||
end)
|
|
||||||
|
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
world:set(B, jecs.OnRemove, function()
|
local C = world:component()
|
||||||
called += 1
|
|
||||||
end)
|
|
||||||
|
|
||||||
local e = world:entity()
|
do
|
||||||
|
local id1 = world:entity()
|
||||||
|
local id2 = world:entity()
|
||||||
|
local id3 = world:entity()
|
||||||
|
|
||||||
world:set(e, A, true)
|
world:set(id1, A, true)
|
||||||
world:set(e, B, true)
|
|
||||||
|
|
||||||
world:clear(e)
|
world:set(id2, A, true)
|
||||||
|
world:set(id2, B, true)
|
||||||
|
|
||||||
|
world:set(id3, A, true)
|
||||||
|
world:set(id3, B, true)
|
||||||
|
world:set(id3, C, true)
|
||||||
|
|
||||||
|
world:clear(A)
|
||||||
|
|
||||||
|
CHECK(not world:has(id1, A))
|
||||||
|
CHECK(not world:has(id2, A))
|
||||||
|
CHECK(not world:has(id3, A))
|
||||||
|
|
||||||
|
CHECK(world:has(id2, B))
|
||||||
|
CHECK(world:has(id3, B, C))
|
||||||
|
|
||||||
|
world:clear(C)
|
||||||
|
|
||||||
|
CHECK(world:has(id2, B))
|
||||||
|
CHECK(world:has(id3, B))
|
||||||
|
|
||||||
|
CHECK(world:contains(A))
|
||||||
|
CHECK(world:contains(C))
|
||||||
|
CHECK(world:has(A, jecs.Component))
|
||||||
|
CHECK(world:has(B, jecs.Component))
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local id1 = world:entity()
|
||||||
|
local id2 = world:entity()
|
||||||
|
local id3 = world:entity()
|
||||||
|
|
||||||
|
local tgt = world:entity()
|
||||||
|
|
||||||
|
world:add(id1, pair(A, tgt))
|
||||||
|
world:add(id1, pair(B, tgt))
|
||||||
|
world:add(id1, pair(C, tgt))
|
||||||
|
|
||||||
|
world:add(id2, pair(A, tgt))
|
||||||
|
world:add(id2, pair(B, tgt))
|
||||||
|
world:add(id2, pair(C, tgt))
|
||||||
|
|
||||||
|
world:add(id3, pair(A, tgt))
|
||||||
|
world:add(id3, pair(B, tgt))
|
||||||
|
world:add(id3, pair(C, tgt))
|
||||||
|
|
||||||
|
world:clear(B)
|
||||||
|
CHECK(world:has(id1, pair(A, tgt), pair(C, tgt)))
|
||||||
|
CHECK(not world:has(id1, pair(B, tgt)))
|
||||||
|
CHECK(world:has(id2, pair(A, tgt), pair(C, tgt)))
|
||||||
|
CHECK(not world:has(id1, pair(B, tgt)))
|
||||||
|
CHECK(world:has(id3, pair(A, tgt), pair(C, tgt)))
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
CHECK(world:contains(e))
|
|
||||||
CHECK(not world:has(e, A))
|
|
||||||
CHECK(not world:has(e, B))
|
|
||||||
CHECK(called == 2)
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -770,60 +756,6 @@ TEST("world:contains()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:delete()", function()
|
TEST("world:delete()", function()
|
||||||
do CASE "OnDelete cleanup policy cascades deletion to entites with idr_r pairs"
|
|
||||||
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
|
|
||||||
|
|
||||||
do CASE "OnDeleteTarget works correctly regardless of adjacent archetype iteration order"
|
|
||||||
local world = jecs.world()
|
|
||||||
local t = world:entity()
|
|
||||||
local c = world:component()
|
|
||||||
world:add(c, t)
|
|
||||||
|
|
||||||
local component = world:component()
|
|
||||||
local lifetime = world:component()
|
|
||||||
|
|
||||||
local tag = world:entity()
|
|
||||||
local rel1 = world:entity()
|
|
||||||
local rel2 = world:entity()
|
|
||||||
local rel3 = world:entity()
|
|
||||||
|
|
||||||
local destroyed = false
|
|
||||||
|
|
||||||
world:removed(lifetime, function(e)
|
|
||||||
destroyed = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
local parent = world:entity()
|
|
||||||
world:set(parent, component, "foo")
|
|
||||||
world:add(parent, jecs.pair(rel1, component))
|
|
||||||
|
|
||||||
local other1 = world:entity()
|
|
||||||
world:add(other1, tag)
|
|
||||||
world:add(other1, jecs.pair(jecs.ChildOf, parent))
|
|
||||||
world:add(other1, jecs.pair(rel1, component))
|
|
||||||
|
|
||||||
local child = world:entity()
|
|
||||||
world:set(child, lifetime, "")
|
|
||||||
world:add(child, jecs.pair(jecs.ChildOf, parent))
|
|
||||||
world:add(child, jecs.pair(rel3, parent))
|
|
||||||
world:add(child, jecs.pair(rel2, other1))
|
|
||||||
|
|
||||||
world:delete(parent)
|
|
||||||
|
|
||||||
CHECK(destroyed)
|
|
||||||
CHECK(not world:contains(child))
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "Should delete children in different archetypes if they have the same parent"
|
do CASE "Should delete children in different archetypes if they have the same parent"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
|
||||||
|
@ -878,6 +810,7 @@ TEST("world:delete()", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
world:set(A, jecs.OnRemove, function(entity, id)
|
world:set(A, jecs.OnRemove, function(entity, id)
|
||||||
world:set(entity, B, true)
|
world:set(entity, B, true)
|
||||||
end)
|
end)
|
||||||
|
@ -1447,7 +1380,7 @@ TEST("world:range()", function()
|
||||||
CHECK(world.entity_index.alive_count == 400)
|
CHECK(world.entity_index.alive_count == 400)
|
||||||
CHECK(e)
|
CHECK(e)
|
||||||
end
|
end
|
||||||
do CASE "entity ID reuse works correctly across different world ranges"
|
do CASE "axen"
|
||||||
local base = jecs.world()
|
local base = jecs.world()
|
||||||
base:range(1_000, 2_000)
|
base:range(1_000, 2_000)
|
||||||
|
|
||||||
|
@ -1559,24 +1492,6 @@ TEST("world:range()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:entity()", function()
|
TEST("world:entity()", function()
|
||||||
do CASE "entity mirroring preserves IDs across world ranges"
|
|
||||||
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)
|
|
||||||
CHECK(world:contains(e0v1)) -- fails
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "component names persist after entity creation"
|
|
||||||
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
|
|
||||||
do CASE "desired id"
|
do CASE "desired id"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local id = world:entity()
|
local id = world:entity()
|
||||||
|
|
Loading…
Reference in a new issue