Skip to content

Commit

Permalink
Add changetracking example
Browse files Browse the repository at this point in the history
  • Loading branch information
Ukendio committed Aug 3, 2024
1 parent 6759ae0 commit 3577d0b
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 8 deletions.
242 changes: 242 additions & 0 deletions examples/luau/changetracking.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
local jecs = require("@jecs")

type World = jecs.WorldShim

type Tracker<T> = { 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<T> = number & { __nominal_type_dont_use: T }

local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
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
4 changes: 2 additions & 2 deletions examples/luau/entities/basics.luau
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
8 changes: 4 additions & 4 deletions examples/luau/entities/hierarchy.luau
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
4 changes: 2 additions & 2 deletions examples/luau/queries/basics.luau
Original file line number Diff line number Diff line change
@@ -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()
Expand Down

0 comments on commit 3577d0b

Please sign in to comment.