From af093713b4d82f0b2be926a6dd51083d8b736242 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Fri, 29 Aug 2025 17:13:13 +0200 Subject: [PATCH] Add examples --- examples/luau/entities/basics.luau | 21 ++++--- examples/luau/queries/archetypes/targets.luau | 60 ++++++++++++++++++ .../archetypes/visibility_cascades.luau | 44 +++++++++++++ examples/luau/queries/basics.luau | 2 +- examples/luau/queries/changetracking.luau | 31 ++++----- examples/luau/queries/spatial_grids.luau | 63 ++++++++++++------- 6 files changed, 173 insertions(+), 48 deletions(-) create mode 100755 examples/luau/queries/archetypes/targets.luau create mode 100755 examples/luau/queries/archetypes/visibility_cascades.luau diff --git a/examples/luau/entities/basics.luau b/examples/luau/entities/basics.luau index 170431e..ef92209 100755 --- a/examples/luau/entities/basics.luau +++ b/examples/luau/entities/basics.luau @@ -1,15 +1,19 @@ local jecs = require("@jecs") -local world = jecs.World.new() +local world = jecs.world() -local Position = world:component() +local Position = world:component() :: jecs.Id local Walking = world:component() -local Name = world:component() +local Name = world:component() :: jecs.Id + +local function name(e: jecs.Entity): string + return assert(world:get(e, Name)) +end -- Create an entity with name Bob local bob = world:entity() -- The set operation finds or creates a component, and sets it. -world:set(bob, Position, Vector3.new(10, 20, 30)) +world:set(bob, Position, vector.create(10, 20, 30)) -- Name the entity Bob world:set(bob, Name, "Bob") -- The add operation adds a component without setting a value. This is @@ -18,15 +22,16 @@ world:add(bob, Walking) -- Get the value for the Position component local pos = world:get(bob, Position) -print(`\{{pos.X}, {pos.Y}, {pos.Z}\}`) +assert(pos) +print(`\{{pos.x}, {pos.y}, {pos.z}\}`) -- Overwrite the value of the Position component -world:set(bob, Position, Vector3.new(40, 50, 60)) +world:set(bob, Position, vector.create(40, 50, 60)) local alice = world:entity() -- Create another named entity world:set(alice, Name, "Alice") -world:set(alice, Position, Vector3.new(10, 20, 30)) +world:set(alice, Position, vector.create(10, 20, 30)) world:add(alice, Walking) -- Remove tag @@ -34,7 +39,7 @@ world:remove(alice, Walking) -- Iterate all entities with Position for entity, p in world:query(Position) do - print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`) + print(`{name(entity)}: \{{p.x}, {p.y}, {p.z}\}`) end -- Output: diff --git a/examples/luau/queries/archetypes/targets.luau b/examples/luau/queries/archetypes/targets.luau new file mode 100755 index 0000000..5db27a8 --- /dev/null +++ b/examples/luau/queries/archetypes/targets.luau @@ -0,0 +1,60 @@ +-- 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 diff --git a/examples/luau/queries/archetypes/visibility_cascades.luau b/examples/luau/queries/archetypes/visibility_cascades.luau new file mode 100755 index 0000000..3228fa5 --- /dev/null +++ b/examples/luau/queries/archetypes/visibility_cascades.luau @@ -0,0 +1,44 @@ +-- 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 +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 diff --git a/examples/luau/queries/basics.luau b/examples/luau/queries/basics.luau index 21e7cae..0a801ba 100755 --- a/examples/luau/queries/basics.luau +++ b/examples/luau/queries/basics.luau @@ -1,5 +1,5 @@ local jecs = require("@jecs") -local world = jecs.World.new() +local world = jecs.world() local Position = world:component() local Velocity = world:component() diff --git a/examples/luau/queries/changetracking.luau b/examples/luau/queries/changetracking.luau index e8e23ea..1fbb2a4 100755 --- a/examples/luau/queries/changetracking.luau +++ b/examples/luau/queries/changetracking.luau @@ -1,19 +1,16 @@ local jecs = require("@jecs") local pair = jecs.pair -local world = jecs.World.new() -local Name = world:component() +local world = jecs.world() +local Name = world:component() :: jecs.Id -local function named(ctr, name) - local e = ctr(world) - world:set(e, Name, name) - return e -end -local function name(e) - return world:get(e, Name) + +local function name(e: jecs.Entity): string + return assert(world:get(e, Name)) end -local Position = named(world.component, "Position") :: jecs.Entity +local Position = world:component() :: jecs.Id +world:set(Position, Name, "Position") local Previous = jecs.Rest local added = world @@ -29,10 +26,14 @@ local removed = world :cached() -local e1 = named(world.entity, "e1") +local e1 = world:entity() +world:set(e1, Name, "e1") 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)) + for entity, p in added do print(`Added {name(entity)}: \{{p.x}, {p.y}, {p.z}}`) world:set(entity, pair(Previous, Position), p) @@ -40,10 +41,10 @@ end world:set(e1, Position, vector.create(999, 999, 1998)) -for _, archetype in changed:archetypes() do +for entity, new, old in changed do if new ~= old then - print(`{name(e)}'s Position changed from \{{old.x}, {old.y}, {old.z}\} to \{{new.x}, {new.y}, {new.z}\}`) - world:set(e, pair(Previous, Position), new) + print(`{name(entity)}'s Position changed from \{{old.x}, {old.y}, {old.z}\} to \{{new.x}, {new.y}, {new.z}\}`) + world:set(entity, pair(Previous, Position), new) end end diff --git a/examples/luau/queries/spatial_grids.luau b/examples/luau/queries/spatial_grids.luau index 6dcf72f..5dcc268 100755 --- a/examples/luau/queries/spatial_grids.luau +++ b/examples/luau/queries/spatial_grids.luau @@ -3,32 +3,47 @@ local pair = jecs.pair local ChildOf = jecs.ChildOf local __ = jecs.Wildcard local Name = jecs.Name -local world = jecs.World.new() +local world = jecs.world() -type Id = number & { __T: T } -local Voxel = world:component() :: Id -local Position = world:component() :: Id -local Perception = world:component() :: Id<{ +local Voxel = world:component() :: jecs.Id +local Position = world:component() :: jecs.Id +local Perception = world:component() :: jecs.Id<{ range: number, fov: number, - dir: Vector3, + dir: vector, }> -local PrimaryPart = world:component() :: Id +type part = { + Position: vector +} +local PrimaryPart = world:component() :: jecs.Id -local local_player = game:GetService("Players").LocalPlayer +local local_player = { + Character = { + PrimaryPart = { + Position = vector.create(50, 0, 30) + } + } +} +local workspace = { + CurrentCamera = { + CFrame = { + LookVector = vector.create(0, 0, -1) + } + } +} -local function distance(a: Vector3, b: Vector3) - return (b - a).Magnitude +local function distance(a: vector, b: vector) + return vector.magnitude((b - a)) end -local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, fov_angle: number) +local function is_in_fov(a: vector, b: vector, forward_dir: vector, fov_angle: number) local to_target = b - a - local forward_xz = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit - local to_target_xz = Vector3.new(to_target.X, 0, to_target.Z).Unit + local forward_xz = vector.normalize(vector.create(forward_dir.x, 0, forward_dir.z)) + local to_target_xz = vector.normalize(vector.create(to_target.x, 0, to_target.z)) - 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.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 angle_difference = math.abs(forward_angle - angle_to_target) @@ -42,7 +57,7 @@ end local map = {} local grid = 50 -local function add_to_voxel(source: number, position: Vector3, prev_voxel_id: number?) +local function add_to_voxel(source: jecs.Entity, position: vector, prev_voxel_id: jecs.Entity?) local hash = position // grid local voxel_id = map[hash] if not voxel_id then @@ -79,7 +94,7 @@ local function update_camera_direction(dt: number) end local function perceive_enemies(dt: number) - local it = world:query(Perception, Position, PrimaryPart) + local it = world:query(Perception, Position, PrimaryPart):iter() -- There is only going to be one entity matching the query local e, self_perception, self_position, self_primary_part = it() @@ -93,28 +108,28 @@ local function perceive_enemies(dt: number) if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then 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 local player = world:entity() world:set(player, Perception, { - range = 100, + range = 200, fov = 90, - dir = Vector3.new(1, 0, 0), + dir = vector.create(1, 0, 0), }) world:set(player, Name, "LocalPlayer") -local primary_part = (local_player.Character :: Model).PrimaryPart :: Part +local primary_part = local_player.Character.PrimaryPart world:set(player, PrimaryPart, primary_part) -world:set(player, Position, Vector3.zero) +world:set(player, Position, vector.zero) local enemy = world:entity() world:set(enemy, Name, "Enemy $1") -world:set(enemy, Position, Vector3.new(50, 0, 20)) +world:set(enemy, Position, vector.create(50, 0, 20)) add_to_voxel(player, primary_part.Position) -add_to_voxel(enemy, world) +add_to_voxel(enemy, assert(world:get(enemy, Position))) local dt = 1 / 60 reconcile_client_owned_assembly_to_voxel(dt)