From 3577d0b84d86f3916f75aa7f010339b08aa509c6 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Sat, 3 Aug 2024 05:36:32 +0200 Subject: [PATCH] Add changetracking example --- examples/luau/changetracking.luau | 242 ++++++++++++++++++++++++++ examples/luau/entities/basics.luau | 4 +- examples/luau/entities/hierarchy.luau | 8 +- examples/luau/queries/basics.luau | 4 +- 4 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 examples/luau/changetracking.luau diff --git a/examples/luau/changetracking.luau b/examples/luau/changetracking.luau new file mode 100644 index 00000000..20ed8884 --- /dev/null +++ b/examples/luau/changetracking.luau @@ -0,0 +1,242 @@ +local jecs = require("@jecs") + +type World = jecs.WorldShim + +type Tracker = { track: (world: World, fn: (changes: { + added: () -> () -> (number, T), + removed: () -> () -> number, + changed: () -> () -> (number, T, T) + }) -> ()) -> () +} + +local function diff(a, b) + local size = 0 + for k, v in a do + if b[k] ~= v then + return true + end + size += 1 + end + for k, v in b do + size -= 1 + end + + if size ~= 0 then + return true + end + + return false +end + +type Entity = number & { __nominal_type_dont_use: T } + +local function ChangeTracker(world, T: Entity): Tracker + local PreviousT = jecs.pair(jecs.Rest, T) + local add = {} + local added + local removed + local is_trivial + + local function changes_added() + added = true + local q = world:query(T):without(PreviousT):drain() + return function() + local id, data = q.next() + if not id then + return nil + end + + is_trivial = typeof(data) ~= "table" + + add[id] = data + + return id, data + end + end + + local function changes_changed() + local q = world:query(T, PreviousT):drain() + + return function() + local id, new, old = q.next() + while true do + if not id then + return nil + end + + if not is_trivial then + if diff(new, old) then + break + end + elseif new ~= old then + break + end + + id, new, old = q.next() + end + + add[id] = new + + return id, old, new + end + end + + local function changes_removed() + removed = true + + local q = world:query(PreviousT):without(T):drain() + return function() + local id = q.next() + if id then + world:remove(id, PreviousT) + end + return id + end + end + + local changes = { + added = changes_added, + changed = changes_changed, + removed = changes_removed, + } + + local function track(fn) + added = false + removed = false + + fn(changes) + + if not added then + for _ in changes_added() do + end + end + + if not removed then + for _ in changes_removed() do + end + end + + for e, data in add do + world:set(e, PreviousT, if is_trivial then data else table.clone(data)) + end + end + + local tracker = { track = track } + + return tracker +end + +local Vector3 +do + Vector3 = {} + Vector3.__index = Vector3 + + function Vector3.new(x, y, z) + x = x or 0 + y = y or 0 + z = z or 0 + return setmetatable({ X = x, Y = y, Z = z }, Vector3) + end + + function Vector3.__add(left, right) + return Vector3.new( + left.X + right.X, + left.Y + right.Y, + left.Z + right.Z + ) + end + + function Vector3.__mul(left, right) + if typeof(right) == "number" then + return Vector3.new( + left.X * right, + left.Y * right, + left.Z * right + ) + end + return Vector3.new( + left.X * right.X, + left.Y * right.Y, + left.Z * right.Z + ) + end + + Vector3.one = Vector3.new(1, 1, 1) + Vector3.zero = Vector3.new() +end + +local world = jecs.World.new() +local Name = world:component() + +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) +end + +local Position = named(world.component, "Position") + +-- Create the ChangeTracker with the component type to track +local PositionTracker = ChangeTracker(world, Position) + +local e1 = named(world.entity, "e1") +world:set(e1, Position, Vector3.new(10, 20, 30)) + +local e2 = named(world.entity, "e2") +world:set(e2, Position, Vector3.new(10, 20, 30)) + +PositionTracker.track(function(changes) + -- You can iterate over different types of changes: Added, Changed, Removed + + -- added queries for every entity with a new Position component + for e, p in changes.added() do + print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`) + end + + -- changed queries for entities who's changed their data since + -- last was it tracked + for _ in changes.changed() do + print([[This won't print because it is the first time + we are tracking the Position component]]) + end + + -- removed queries for entities who's removed their Position component + -- since last it was tracked + for _ in changes.removed() do + print([[This won't print because it is the first time + we are tracking the Position component]]) + end +end) + +world:set(e1, Position, Vector3.new(1, 1, 2) * 999) + +PositionTracker.track(function(changes) + for e, p in changes.added() do + print([[This won't never print no Position component was added + since last time we tracked]]) + end + + for e, old, new in changes.changed() do + print(`{name(e)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`) + end + + -- If you don't call e.g. changes.removed() then it will automatically drain its iterator and stage their changes. + -- This ensures you will not have any off-by-one frame errors. +end) + +world:remove(e2, Position) + +PositionTracker.track(function(changes) + for e in changes.removed() do + print(`Position was removed from {name(e)}`) + end +end) + +-- Output: +-- Added 265: {10, 20, 30} +-- Added 264: {10, 20, 30} +-- e1's Position changed from {10, 20, 30} to {999, 999, 1998} +-- Position was removed from e2 diff --git a/examples/luau/entities/basics.luau b/examples/luau/entities/basics.luau index 5833b3e0..cfebf6d8 100644 --- a/examples/luau/entities/basics.luau +++ b/examples/luau/entities/basics.luau @@ -1,5 +1,5 @@ -local ecs = require("@jecs") -local world = ecs.World.new() +local jecs = require("@jecs") +local world = jecs.World.new() local Position = world:component() local Walking = world:component() diff --git a/examples/luau/entities/hierarchy.luau b/examples/luau/entities/hierarchy.luau index 2882be46..d8725095 100644 --- a/examples/luau/entities/hierarchy.luau +++ b/examples/luau/entities/hierarchy.luau @@ -1,7 +1,7 @@ -local ecs = require("@jecs") -local pair = ecs.pair -local ChildOf = ecs.ChildOf -local world = ecs.World.new() +local jecs = require("@jecs") +local pair = jecs.pair +local ChildOf = jecs.ChildOf +local world = jecs.World.new() local Name = world:component() local Position = world:component() diff --git a/examples/luau/queries/basics.luau b/examples/luau/queries/basics.luau index b085e9e5..8dd9a35f 100644 --- a/examples/luau/queries/basics.luau +++ b/examples/luau/queries/basics.luau @@ -1,5 +1,5 @@ -local ecs = require("@jecs") -local world = ecs.World.new() +local jecs = require("@jecs") +local world = jecs.World.new() local Position = world:component() local Velocity = world:component()