style: fix inconsistent project formatting

Closes #138
This commit is contained in:
christopher-buss 2024-10-12 20:28:41 +01:00
parent f82318c642
commit e81b572480
45 changed files with 2867 additions and 2855 deletions

View file

@ -14,7 +14,7 @@ jobs:
- name: Run Stylua - name: Run Stylua
uses: JohnnyMorganz/stylua-action@v4 uses: JohnnyMorganz/stylua-action@v4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
version: latest # NOTE: we recommend pinning to a specific version in case of formatting changes version: latest # NOTE: we recommend pinning to a specific version in case of formatting changes
# CLI arguments # CLI arguments
args: ./src args: --check ./src

View file

@ -1,4 +1,3 @@
local jecs = require("@jecs") local jecs = require("@jecs")
local mirror = require("../mirror/init") local mirror = require("../mirror/init")
@ -39,36 +38,36 @@ do
if flip() then if flip() then
combination ..= "B" combination ..= "B"
ecs:set(entity, D2, {value = true}) ecs:set(entity, D2, { value = true })
end end
if flip() then if flip() then
combination ..= "C" combination ..= "C"
ecs:set(entity, D3, {value = true}) ecs:set(entity, D3, { value = true })
end end
if flip() then if flip() then
combination ..= "D" combination ..= "D"
ecs:set(entity, D4, {value = true}) ecs:set(entity, D4, { value = true })
end end
if flip() then if flip() then
combination ..= "E" combination ..= "E"
ecs:set(entity, D5, {value = true}) ecs:set(entity, D5, { value = true })
end end
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:set(entity, D6, {value = true}) ecs:set(entity, D6, { value = true })
end end
if flip() then if flip() then
combination ..= "G" combination ..= "G"
ecs:set(entity, D7, {value = true}) ecs:set(entity, D7, { value = true })
end end
if flip() then if flip() then
combination ..= "H" combination ..= "H"
ecs:set(entity, D8, {value = true}) ecs:set(entity, D8, { value = true })
end end
if #combination == 7 then if #combination == 7 then
added += 1 added += 1
ecs:set(entity, D1, {value = true}) ecs:set(entity, D1, { value = true })
end end
archetypes[combination] = true archetypes[combination] = true
end end
@ -117,36 +116,36 @@ do
if flip() then if flip() then
combination ..= "B" combination ..= "B"
ecs:set(entity, D2, {value = true}) ecs:set(entity, D2, { value = true })
end end
if flip() then if flip() then
combination ..= "C" combination ..= "C"
ecs:set(entity, D3, {value = true}) ecs:set(entity, D3, { value = true })
end end
if flip() then if flip() then
combination ..= "D" combination ..= "D"
ecs:set(entity, D4, {value = true}) ecs:set(entity, D4, { value = true })
end end
if flip() then if flip() then
combination ..= "E" combination ..= "E"
ecs:set(entity, D5, {value = true}) ecs:set(entity, D5, { value = true })
end end
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:set(entity, D6, {value = true}) ecs:set(entity, D6, { value = true })
end end
if flip() then if flip() then
combination ..= "G" combination ..= "G"
ecs:set(entity, D7, {value = true}) ecs:set(entity, D7, { value = true })
end end
if flip() then if flip() then
combination ..= "H" combination ..= "H"
ecs:set(entity, D8, {value = true}) ecs:set(entity, D8, { value = true })
end end
if #combination == 7 then if #combination == 7 then
added += 1 added += 1
ecs:set(entity, D1, {value = true}) ecs:set(entity, D1, { value = true })
end end
archetypes[combination] = true archetypes[combination] = true
end end

View file

@ -8,11 +8,12 @@ local function TITLE(s: string)
print(testkit.color.white(s)) print(testkit.color.white(s))
end end
local N = 2^17 local N = 2 ^ 17
local pair = jecs.pair local pair = jecs.pair
do TITLE "create" do
TITLE("create")
local world = jecs.World.new() local world = jecs.World.new()
BENCH("entity", function() BENCH("entity", function()
@ -22,17 +23,17 @@ do TITLE "create"
end) end)
local A = world:component() local A = world:component()
local B = world:component() local B = world:component()
BENCH("pair", function() BENCH("pair", function()
for i = 1, START(N) do for i = 1, START(N) do
jecs.pair(A, B) jecs.pair(A, B)
end end
end) end)
end end
do TITLE "set" do
TITLE("set")
local world = jecs.World.new() local world = jecs.World.new()
local A = world:entity() local A = world:entity()
@ -65,7 +66,8 @@ end
-- we have a separate benchmark for relationships. -- we have a separate benchmark for relationships.
-- this is due to that relationships have a very high id compared to normal -- this is due to that relationships have a very high id compared to normal
-- components, which cause them to get added into the hashmap portion. -- components, which cause them to get added into the hashmap portion.
do TITLE "set relationship" do
TITLE("set relationship")
local world = jecs.World.new() local world = jecs.World.new()
local A = world:entity() local A = world:entity()
@ -98,7 +100,8 @@ do TITLE "set relationship"
end) end)
end end
do TITLE "get" do
TITLE("get")
local world = jecs.World.new() local world = jecs.World.new()
local A = world:component() local A = world:component()
@ -140,7 +143,8 @@ do TITLE "get"
end) end)
end end
do TITLE "target" do
TITLE("target")
BENCH("1st target", function() BENCH("1st target", function()
local world = jecs.World.new() local world = jecs.World.new()
@ -164,14 +168,14 @@ do TITLE "target"
world:target(entities[i], A, 0) world:target(entities[i], A, 0)
end end
end) end)
end end
--- this benchmark is used to view how fragmentation affects query performance --- this benchmark is used to view how fragmentation affects query performance
--- we use this by determining how many entities should fit per arcehtype, instead --- we use this by determining how many entities should fit per arcehtype, instead
--- of creating x amount of archetypes. this would scale better with any amount of --- of creating x amount of archetypes. this would scale better with any amount of
--- entities. --- entities.
do TITLE(`query {N} entities`) do
TITLE(`query {N} entities`)
local function view_bench(n: number) local function view_bench(n: number)
BENCH(`{n} entities per archetype`, function() BENCH(`{n} entities per archetype`, function()
@ -194,14 +198,13 @@ do TITLE(`query {N} entities`)
end end
end end
START() START()
for id in world:query(A, B, C, D) do for id in world:query(A, B, C, D) do
end end
end) end)
BENCH(`inlined query`, function() BENCH(`inlined query`, function()
local world = jecs.World.new() local world = jecs.World.new()
local A = world:component() local A = world:component()
local B = world:component() local B = world:component()
local C = world:component() local C = world:component()
@ -219,15 +222,14 @@ do TITLE(`query {N} entities`)
end end
end end
START() START()
for _, archetype in world:query(A, B, C, D):archetypes() do for _, archetype in world:query(A, B, C, D):archetypes() do
local columns, records = archetype.columns, archetype.records local columns, records = archetype.columns, archetype.records
local a = columns[records[A].column] local a = columns[records[A].column]
local b = columns[records[B].column] local b = columns[records[B].column]
local c = columns[records[C].column] local c = columns[records[C].column]
local d = columns[records[D].column] local d = columns[records[D].column]
for row in archetype.entities do for row in archetype.entities do
local _1, _2, _3, _4 = a[row], b[row], c[row], d[row] local _1, _2, _3, _4 = a[row], b[row], c[row], d[row]
end end
end end
@ -235,7 +237,6 @@ do TITLE(`query {N} entities`)
end end
for i = 13, 0, -1 do for i = 13, 0, -1 do
view_bench(2^i) view_bench(2 ^ i)
end end
end end

View file

@ -20,7 +20,7 @@ do
TITLE("one component in common") TITLE("one component in common")
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53) local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
BENCH("1 component", function() BENCH("1 component", function()
for _ in world:query(A) do for _ in world:query(A) do
end end
end) end)
@ -86,36 +86,36 @@ do
if flip() then if flip() then
combination ..= "B" combination ..= "B"
ecs:set(entity, D2, {value = true}) ecs:set(entity, D2, { value = true })
end end
if flip() then if flip() then
combination ..= "C" combination ..= "C"
ecs:set(entity, D3, {value = true}) ecs:set(entity, D3, { value = true })
end end
if flip() then if flip() then
combination ..= "D" combination ..= "D"
ecs:set(entity, D4, {value = true}) ecs:set(entity, D4, { value = true })
end end
if flip() then if flip() then
combination ..= "E" combination ..= "E"
ecs:set(entity, D5, {value = true}) ecs:set(entity, D5, { value = true })
end end
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:set(entity, D6, {value = true}) ecs:set(entity, D6, { value = true })
end end
if flip() then if flip() then
combination ..= "G" combination ..= "G"
ecs:set(entity, D7, {value = true}) ecs:set(entity, D7, { value = true })
end end
if flip() then if flip() then
combination ..= "H" combination ..= "H"
ecs:set(entity, D8, {value = true}) ecs:set(entity, D8, { value = true })
end end
if #combination == 7 then if #combination == 7 then
added += 1 added += 1
ecs:set(entity, D1, {value = true}) ecs:set(entity, D1, { value = true })
end end
archetypes[combination] = true archetypes[combination] = true
end end
@ -202,36 +202,36 @@ do
if flip() then if flip() then
combination ..= "B" combination ..= "B"
ecs:set(entity, D2, {value = true}) ecs:set(entity, D2, { value = true })
end end
if flip() then if flip() then
combination ..= "C" combination ..= "C"
ecs:set(entity, D3, {value = true}) ecs:set(entity, D3, { value = true })
end end
if flip() then if flip() then
combination ..= "D" combination ..= "D"
ecs:set(entity, D4, {value = true}) ecs:set(entity, D4, { value = true })
end end
if flip() then if flip() then
combination ..= "E" combination ..= "E"
ecs:set(entity, D5, {value = true}) ecs:set(entity, D5, { value = true })
end end
if flip() then if flip() then
combination ..= "F" combination ..= "F"
ecs:set(entity, D6, {value = true}) ecs:set(entity, D6, { value = true })
end end
if flip() then if flip() then
combination ..= "G" combination ..= "G"
ecs:set(entity, D7, {value = true}) ecs:set(entity, D7, { value = true })
end end
if flip() then if flip() then
combination ..= "H" combination ..= "H"
ecs:set(entity, D8, {value = true}) ecs:set(entity, D8, { value = true })
end end
if #combination == 7 then if #combination == 7 then
added += 1 added += 1
ecs:set(entity, D1, {value = true}) ecs:set(entity, D1, { value = true })
end end
archetypes[combination] = true archetypes[combination] = true
end end

View file

@ -12,37 +12,35 @@ local ecs = jecs.World.new()
local A, B = Matter.component(), Matter.component() local A, B = Matter.component(), Matter.component()
local C, D = ecs:component(), ecs:component() local C, D = ecs:component(), ecs:component()
return { return {
ParameterGenerator = function() ParameterGenerator = function()
local matter_entities = {} local matter_entities = {}
local jecs_entities = {} local jecs_entities = {}
local entities = { local entities = {
matter = matter_entities, matter = matter_entities,
jecs = jecs_entities jecs = jecs_entities,
} }
for i = 1, 1000 do for i = 1, 1000 do
table.insert(matter_entities, newWorld:spawn(A(), B())) table.insert(matter_entities, newWorld:spawn(A(), B()))
local e = ecs:entity() local e = ecs:entity()
ecs:set(e, C, {}) ecs:set(e, C, {})
ecs:set(e, D, {}) ecs:set(e, D, {})
table.insert(jecs_entities, e) table.insert(jecs_entities, e)
end end
return entities return entities
end; end,
Functions = { Functions = {
Matter = function(_, entities) Matter = function(_, entities)
for _, entity in entities.matter do for _, entity in entities.matter do
newWorld:despawn(entity) newWorld:despawn(entity)
end end
end; end,
Jecs = function(_, entities) Jecs = function(_, entities)
for _, entity in entities.jecs do for _, entity in entities.jecs do
ecs:delete(entity) ecs:delete(entity)
end end
end; end,
}; },
} }

View file

@ -3,8 +3,8 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.DevPackages.Matter) local Matter = require(ReplicatedStorage.DevPackages.Matter)
local jecs = require(ReplicatedStorage.Lib)
local ecr = require(ReplicatedStorage.DevPackages.ecr) local ecr = require(ReplicatedStorage.DevPackages.ecr)
local jecs = require(ReplicatedStorage.Lib)
local newWorld = Matter.World.new() local newWorld = Matter.World.new()
local ecs = jecs.World.new() local ecs = jecs.World.new()
local mirror = require(ReplicatedStorage.mirror) local mirror = require(ReplicatedStorage.mirror)
@ -45,7 +45,6 @@ local E6 = mcs:entity()
local E7 = mcs:entity() local E7 = mcs:entity()
local E8 = mcs:entity() local E8 = mcs:entity()
local registry2 = ecr.registry() local registry2 = ecr.registry()
return { return {
ParameterGenerator = function() ParameterGenerator = function()
@ -54,50 +53,48 @@ return {
Functions = { Functions = {
Matter = function() Matter = function()
local e = newWorld:spawn() local e = newWorld:spawn()
for i = 1, 5000 do for i = 1, 5000 do
newWorld:insert(e, newWorld:insert(
A1({ value = true }), e,
A2({ value = true }), A1({ value = true }),
A3({ value = true }), A2({ value = true }),
A4({ value = true }), A3({ value = true }),
A5({ value = true }), A4({ value = true }),
A6({ value = true }), A5({ value = true }),
A7({ value = true }), A6({ value = true }),
A8({ value = true }) A7({ value = true }),
) A8({ value = true })
end )
end
end, end,
ECR = function() ECR = function()
local e = registry2.create() local e = registry2.create()
for i = 1, 5000 do for i = 1, 5000 do
registry2:set(e, B1, {value = false}) registry2:set(e, B1, { value = false })
registry2:set(e, B2, {value = false}) registry2:set(e, B2, { value = false })
registry2:set(e, B3, {value = false}) registry2:set(e, B3, { value = false })
registry2:set(e, B4, {value = false}) registry2:set(e, B4, { value = false })
registry2:set(e, B5, {value = false}) registry2:set(e, B5, { value = false })
registry2:set(e, B6, {value = false}) registry2:set(e, B6, { value = false })
registry2:set(e, B7, {value = false}) registry2:set(e, B7, { value = false })
registry2:set(e, B8, {value = false}) registry2:set(e, B8, { value = false })
end end
end, end,
Jecs = function() Jecs = function()
local e = ecs:entity() local e = ecs:entity()
for i = 1, 5000 do for i = 1, 5000 do
ecs:set(e, C1, {value = false}) ecs:set(e, C1, { value = false })
ecs:set(e, C2, {value = false}) ecs:set(e, C2, { value = false })
ecs:set(e, C3, {value = false}) ecs:set(e, C3, { value = false })
ecs:set(e, C4, {value = false}) ecs:set(e, C4, { value = false })
ecs:set(e, C5, {value = false}) ecs:set(e, C5, { value = false })
ecs:set(e, C6, {value = false}) ecs:set(e, C6, { value = false })
ecs:set(e, C7, {value = false}) ecs:set(e, C7, { value = false })
ecs:set(e, C8, {value = false}) ecs:set(e, C8, { value = false })
end
end
end, end,
}, },
} }

View file

@ -2,9 +2,9 @@
--!native --!native
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local rgb = require(ReplicatedStorage.rgb)
local Matter = require(ReplicatedStorage.DevPackages["_Index"]["matter-ecs_matter@0.8.1"].matter) local Matter = require(ReplicatedStorage.DevPackages["_Index"]["matter-ecs_matter@0.8.1"].matter)
local ecr = require(ReplicatedStorage.DevPackages["_Index"]["centau_ecr@0.8.0"].ecr) local ecr = require(ReplicatedStorage.DevPackages["_Index"]["centau_ecr@0.8.0"].ecr)
local rgb = require(ReplicatedStorage.rgb)
local newWorld = Matter.World.new() local newWorld = Matter.World.new()
local jecs = require(ReplicatedStorage.Lib) local jecs = require(ReplicatedStorage.Lib)
@ -39,7 +39,6 @@ local D6 = ecs:component()
local D7 = ecs:component() local D7 = ecs:component()
local D8 = ecs:component() local D8 = ecs:component()
local E1 = mcs:entity() local E1 = mcs:entity()
local E2 = mcs:entity() local E2 = mcs:entity()
local E3 = mcs:entity() local E3 = mcs:entity()
@ -49,14 +48,13 @@ local E6 = mcs:entity()
local E7 = mcs:entity() local E7 = mcs:entity()
local E8 = mcs:entity() local E8 = mcs:entity()
local registry2 = ecr.registry() local registry2 = ecr.registry()
local function flip() local function flip()
return math.random() >= 0.25 return math.random() >= 0.25
end end
local N = 2^16-2 local N = 2 ^ 16 - 2
local archetypes = {} local archetypes = {}
local hm = 0 local hm = 0
@ -68,69 +66,66 @@ for i = 1, N do
local m = mcs:entity() local m = mcs:entity()
if flip() then if flip() then
registry2:set(id, B1, {value = true}) registry2:set(id, B1, { value = true })
ecs:set(entity, D1, { value = true}) ecs:set(entity, D1, { value = true })
newWorld:insert(n, A1({value = true})) newWorld:insert(n, A1({ value = true }))
mcs:set(m, E1, { value = 2}) mcs:set(m, E1, { value = 2 })
end end
if flip() then if flip() then
combination ..= "B" combination ..= "B"
registry2:set(id, B2, {value = true}) registry2:set(id, B2, { value = true })
ecs:set(entity, D2, { value = true}) ecs:set(entity, D2, { value = true })
mcs:set(m, E2, { value = 2}) mcs:set(m, E2, { value = 2 })
newWorld:insert(n, A2({value = true})) newWorld:insert(n, A2({ value = true }))
end end
if flip() then if flip() then
combination ..= "C" combination ..= "C"
registry2:set(id, B3, {value = true}) registry2:set(id, B3, { value = true })
ecs:set(entity, D3, { value = true}) ecs:set(entity, D3, { value = true })
mcs:set(m, E3, { value = 2}) mcs:set(m, E3, { value = 2 })
newWorld:insert(n, A3({value = true})) newWorld:insert(n, A3({ value = true }))
end end
if flip() then if flip() then
combination ..= "D" combination ..= "D"
registry2:set(id, B4, {value = true}) registry2:set(id, B4, { value = true })
ecs:set(entity, D4, { value = true}) ecs:set(entity, D4, { value = true })
mcs:set(m, E4, { value = 2}) mcs:set(m, E4, { value = 2 })
newWorld:insert(n, A4({value = true})) newWorld:insert(n, A4({ value = true }))
end end
if flip() then if flip() then
combination ..= "E" combination ..= "E"
registry2:set(id, B5, {value = true}) registry2:set(id, B5, { value = true })
ecs:set(entity, D5, { value = true}) ecs:set(entity, D5, { value = true })
mcs:set(m, E5, { value = 2}) mcs:set(m, E5, { value = 2 })
newWorld:insert(n, A5({value = true})) newWorld:insert(n, A5({ value = true }))
end end
if flip() then if flip() then
combination ..= "F" combination ..= "F"
registry2:set(id, B6, {value = true}) registry2:set(id, B6, { value = true })
ecs:set(entity, D6, { value = true}) ecs:set(entity, D6, { value = true })
mcs:set(m, E6, { value = 2}) mcs:set(m, E6, { value = 2 })
newWorld:insert(n, A6({value = true})) newWorld:insert(n, A6({ value = true }))
end end
if flip() then if flip() then
combination ..= "G" combination ..= "G"
registry2:set(id, B7, {value = true}) registry2:set(id, B7, { value = true })
ecs:set(entity, D7, { value = true}) ecs:set(entity, D7, { value = true })
mcs:set(m, E7, { value = 2}) mcs:set(m, E7, { value = 2 })
newWorld:insert(n, A7({value = true})) newWorld:insert(n, A7({ value = true }))
end end
if flip() then if flip() then
combination ..= "H" combination ..= "H"
registry2:set(id, B8, {value = true}) registry2:set(id, B8, { value = true })
newWorld:insert(n, A8({value = true})) newWorld:insert(n, A8({ value = true }))
ecs:set(entity, D8, { value = true}) ecs:set(entity, D8, { value = true })
mcs:set(m, E8, { value = 2}) mcs:set(m, E8, { value = 2 })
end end
if combination:find("BCDF") then if combination:find("BCDF") then
if not archetypes[combination] then if not archetypes[combination] then
print(combination) print(combination)
end end
hm += 1 hm += 1
end end
@ -148,7 +143,7 @@ local WALL = gray(" │ ")
local count = 0 local count = 0
for _, archetype in ecs:query(D2, D4, D6, D8):archetypes() do for _, archetype in ecs:query(D2, D4, D6, D8):archetypes() do
count += #archetype.entities count += #archetype.entities
end end
print(count) print(count)
@ -165,13 +160,13 @@ return {
end, end,
ECR = function() ECR = function()
for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do
end end
end, end,
Jecs = function() Jecs = function()
for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do
end end
end, end,
}, },
} }

View file

@ -14,25 +14,25 @@ return {
local registry2 = ecr.registry() local registry2 = ecr.registry()
return registry2 return registry2
end; end,
Functions = { Functions = {
Matter = function() Matter = function()
for i = 1, 1000 do for i = 1, 1000 do
newWorld:spawn() newWorld:spawn()
end end
end; end,
ECR = function(_, registry2) ECR = function(_, registry2)
for i = 1, 1000 do for i = 1, 1000 do
registry2.create() registry2.create()
end end
end; end,
Jecs = function() Jecs = function()
for i = 1, 1000 do for i = 1, 1000 do
ecs:entity() ecs:entity()
end end
end; end,
}; },
} }

View file

@ -10,11 +10,12 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
if not RunService:IsClient() then if not RunService:IsClient() then
error("Client network module can only be required from the client.") error("Client network module can only be required from the client.")
end end
local Reliable: RemoteEvent = ReplicatedStorage:WaitForChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent local Reliable: RemoteEvent = ReplicatedStorage:WaitForChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent
local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:WaitForChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent local Unreliable: UnreliableRemoteEvent =
ReplicatedStorage:WaitForChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
local Invocations = 0 local Invocations = 0
@ -31,128 +32,128 @@ local RecieveInstances = {}
local RecieveInstanceCursor = 0 local RecieveInstanceCursor = 0
type Entry = { type Entry = {
value: any, value: any,
next: Entry? next: Entry?,
} }
type Queue = { type Queue = {
head: Entry?, head: Entry?,
tail: Entry? tail: Entry?,
} }
type BufferSave = { type BufferSave = {
Size: number, Size: number,
Cursor: number, Cursor: number,
Buffer: buffer, Buffer: buffer,
Instances: {Instance} Instances: { Instance },
} }
local function Read(Bytes: number) local function Read(Bytes: number)
local Offset = RecieveCursor local Offset = RecieveCursor
RecieveCursor += Bytes RecieveCursor += Bytes
return Offset return Offset
end end
local function Save(): BufferSave local function Save(): BufferSave
return { return {
Size = SendSize, Size = SendSize,
Cursor = SendCursor, Cursor = SendCursor,
Buffer = SendBuffer, Buffer = SendBuffer,
Instances = SendInstances Instances = SendInstances,
} }
end end
local function Load(Save: BufferSave?) local function Load(Save: BufferSave?)
if Save then if Save then
SendSize = Save.Size SendSize = Save.Size
SendCursor = Save.Cursor SendCursor = Save.Cursor
SendOffset = Save.Cursor SendOffset = Save.Cursor
SendBuffer = Save.Buffer SendBuffer = Save.Buffer
SendInstances = Save.Instances SendInstances = Save.Instances
return return
end end
SendSize = 64 SendSize = 64
SendCursor = 0 SendCursor = 0
SendOffset = 0 SendOffset = 0
SendBuffer = buffer.create(64) SendBuffer = buffer.create(64)
SendInstances = {} SendInstances = {}
end end
local function Invoke() local function Invoke()
if Invocations == 255 then if Invocations == 255 then
Invocations = 0 Invocations = 0
end end
local Invocation = Invocations local Invocation = Invocations
Invocations += 1 Invocations += 1
return Invocation return Invocation
end end
local function Allocate(Bytes: number) local function Allocate(Bytes: number)
local InUse = (SendCursor + Bytes) local InUse = (SendCursor + Bytes)
if InUse > SendSize then if InUse > SendSize then
--> Avoid resizing the buffer for every write --> Avoid resizing the buffer for every write
while InUse > SendSize do while InUse > SendSize do
SendSize *= 1.5 SendSize *= 1.5
end end
local Buffer = buffer.create(SendSize) local Buffer = buffer.create(SendSize)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
SendBuffer = Buffer SendBuffer = Buffer
end end
SendOffset = SendCursor SendOffset = SendCursor
SendCursor += Bytes SendCursor += Bytes
return SendOffset return SendOffset
end end
local function CreateQueue(): Queue local function CreateQueue(): Queue
return { return {
head = nil, head = nil,
tail = nil tail = nil,
} }
end end
local function Pop(queue: Queue): any local function Pop(queue: Queue): any
local head = queue.head local head = queue.head
if head == nil then if head == nil then
return return
end end
queue.head = head.next queue.head = head.next
return head.value return head.value
end end
local function Push(queue: Queue, value: any) local function Push(queue: Queue, value: any)
local entry: Entry = { local entry: Entry = {
value = value, value = value,
next = nil next = nil,
} }
if queue.tail ~= nil then if queue.tail ~= nil then
queue.tail.next = entry queue.tail.next = entry
end end
queue.tail = entry queue.tail = entry
if queue.head == nil then if queue.head == nil then
queue.head = entry queue.head = entry
end end
end end
local Types = {} local Types = {}
local Calls = table.create(256) local Calls = table.create(256)
local Events: any = { local Events: any = {
Reliable = table.create(256), Reliable = table.create(256),
Unreliable = table.create(256) Unreliable = table.create(256),
} }
local Queue: any = { local Queue: any = {
Reliable = table.create(256), Reliable = table.create(256),
Unreliable = table.create(256) Unreliable = table.create(256),
} }
Queue.Unreliable[0] = CreateQueue() Queue.Unreliable[0] = CreateQueue()
@ -220,21 +221,20 @@ function Types.WriteEVENT_SpawnMob(Value1: number, Value2: CFrame, Value3: numbe
buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3) buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3)
end end
local function StepReplication() local function StepReplication()
if SendCursor <= 0 then if SendCursor <= 0 then
return return
end end
local Buffer = buffer.create(SendCursor) local Buffer = buffer.create(SendCursor)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
Reliable:FireServer(Buffer, SendInstances) Reliable:FireServer(Buffer, SendInstances)
SendSize = 64 SendSize = 64
SendCursor = 0 SendCursor = 0
SendOffset = 0 SendOffset = 0
SendBuffer = buffer.create(64) SendBuffer = buffer.create(64)
table.clear(SendInstances) table.clear(SendInstances)
end end
local Elapsed = 0 local Elapsed = 0
@ -246,13 +246,13 @@ RunService.Heartbeat:Connect(function(DeltaTime: number)
end end
end) end)
Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance}) Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: { Instance })
RecieveCursor = 0 RecieveCursor = 0
RecieveBuffer = Buffer RecieveBuffer = Buffer
RecieveInstances = Instances RecieveInstances = Instances
RecieveInstanceCursor = 0 RecieveInstanceCursor = 0
local Size = buffer.len(RecieveBuffer) local Size = buffer.len(RecieveBuffer)
while (RecieveCursor < Size) do while RecieveCursor < Size do
-- Read BLOCK: 1 bytes -- Read BLOCK: 1 bytes
local BLOCK_START = Read(1) local BLOCK_START = Read(1)
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0) local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
@ -262,13 +262,13 @@ Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
end end
end) end)
Unreliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance}) Unreliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: { Instance })
RecieveCursor = 0 RecieveCursor = 0
RecieveBuffer = Buffer RecieveBuffer = Buffer
RecieveInstances = Instances RecieveInstances = Instances
RecieveInstanceCursor = 0 RecieveInstanceCursor = 0
local Size = buffer.len(RecieveBuffer) local Size = buffer.len(RecieveBuffer)
while (RecieveCursor < Size) do while RecieveCursor < Size do
-- Read BLOCK: 1 bytes -- Read BLOCK: 1 bytes
local BLOCK_START = Read(1) local BLOCK_START = Read(1)
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0) local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
@ -285,7 +285,7 @@ return {
Iter = function(): () -> (number, number, CFrame) Iter = function(): () -> (number, number, CFrame)
local index = 0 local index = 0
local queue = Queue.Unreliable[0] local queue = Queue.Unreliable[0]
return function (): (number, number, CFrame) return function(): (number, number, CFrame)
index += 1 index += 1
local arguments = Pop(queue) local arguments = Pop(queue)
if arguments ~= nil then if arguments ~= nil then
@ -297,7 +297,7 @@ return {
Next = function(): () -> (number, number, CFrame) Next = function(): () -> (number, number, CFrame)
local index = 0 local index = 0
local queue = Queue.Unreliable[0] local queue = Queue.Unreliable[0]
return function (): (number, number, CFrame) return function(): (number, number, CFrame)
index += 1 index += 1
local arguments = Pop(queue) local arguments = Pop(queue)
if arguments ~= nil then if arguments ~= nil then
@ -305,13 +305,13 @@ return {
end end
return return
end end
end end,
}, },
SpawnMob = { SpawnMob = {
Iter = function(): () -> (number, number, CFrame, number) Iter = function(): () -> (number, number, CFrame, number)
local index = 0 local index = 0
local queue = Queue.Reliable[0] local queue = Queue.Reliable[0]
return function (): (number, number, CFrame, number) return function(): (number, number, CFrame, number)
index += 1 index += 1
local arguments = Pop(queue) local arguments = Pop(queue)
if arguments ~= nil then if arguments ~= nil then
@ -323,7 +323,7 @@ return {
Next = function(): () -> (number, number, CFrame, number) Next = function(): () -> (number, number, CFrame, number)
local index = 0 local index = 0
local queue = Queue.Reliable[0] local queue = Queue.Reliable[0]
return function (): (number, number, CFrame, number) return function(): (number, number, CFrame, number)
index += 1 index += 1
local arguments = Pop(queue) local arguments = Pop(queue)
if arguments ~= nil then if arguments ~= nil then
@ -331,7 +331,6 @@ return {
end end
return return
end end
end end,
}, },
} }

View file

@ -11,23 +11,24 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
if not RunService:IsServer() then if not RunService:IsServer() then
error("Server network module can only be required from the server.") error("Server network module can only be required from the server.")
end end
local Reliable: RemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent local Reliable: RemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent
if not Reliable then if not Reliable then
local RemoteEvent = Instance.new("RemoteEvent") local RemoteEvent = Instance.new("RemoteEvent")
RemoteEvent.Name = "BLINK_RELIABLE_REMOTE" RemoteEvent.Name = "BLINK_RELIABLE_REMOTE"
RemoteEvent.Parent = ReplicatedStorage RemoteEvent.Parent = ReplicatedStorage
Reliable = RemoteEvent Reliable = RemoteEvent
end end
local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent local Unreliable: UnreliableRemoteEvent =
ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
if not Unreliable then if not Unreliable then
local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent") local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")
UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE" UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE"
UnreliableRemoteEvent.Parent = ReplicatedStorage UnreliableRemoteEvent.Parent = ReplicatedStorage
Unreliable = UnreliableRemoteEvent Unreliable = UnreliableRemoteEvent
end end
local Invocations = 0 local Invocations = 0
@ -45,131 +46,130 @@ local RecieveInstances = {}
local RecieveInstanceCursor = 0 local RecieveInstanceCursor = 0
type Entry = { type Entry = {
value: any, value: any,
next: Entry? next: Entry?,
} }
type Queue = { type Queue = {
head: Entry?, head: Entry?,
tail: Entry? tail: Entry?,
} }
type BufferSave = { type BufferSave = {
Size: number, Size: number,
Cursor: number, Cursor: number,
Buffer: buffer, Buffer: buffer,
Instances: {Instance} Instances: { Instance },
} }
local function Read(Bytes: number) local function Read(Bytes: number)
local Offset = RecieveCursor local Offset = RecieveCursor
RecieveCursor += Bytes RecieveCursor += Bytes
return Offset return Offset
end end
local function Save(): BufferSave local function Save(): BufferSave
return { return {
Size = SendSize, Size = SendSize,
Cursor = SendCursor, Cursor = SendCursor,
Buffer = SendBuffer, Buffer = SendBuffer,
Instances = SendInstances Instances = SendInstances,
} }
end end
local function Load(Save: BufferSave?) local function Load(Save: BufferSave?)
if Save then if Save then
SendSize = Save.Size SendSize = Save.Size
SendCursor = Save.Cursor SendCursor = Save.Cursor
SendOffset = Save.Cursor SendOffset = Save.Cursor
SendBuffer = Save.Buffer SendBuffer = Save.Buffer
SendInstances = Save.Instances SendInstances = Save.Instances
return return
end end
SendSize = 64 SendSize = 64
SendCursor = 0 SendCursor = 0
SendOffset = 0 SendOffset = 0
SendBuffer = buffer.create(64) SendBuffer = buffer.create(64)
SendInstances = {} SendInstances = {}
end end
local function Invoke() local function Invoke()
if Invocations == 255 then if Invocations == 255 then
Invocations = 0 Invocations = 0
end end
local Invocation = Invocations local Invocation = Invocations
Invocations += 1 Invocations += 1
return Invocation return Invocation
end end
local function Allocate(Bytes: number) local function Allocate(Bytes: number)
local InUse = (SendCursor + Bytes) local InUse = (SendCursor + Bytes)
if InUse > SendSize then if InUse > SendSize then
--> Avoid resizing the buffer for every write --> Avoid resizing the buffer for every write
while InUse > SendSize do while InUse > SendSize do
SendSize *= 1.5 SendSize *= 1.5
end end
local Buffer = buffer.create(SendSize) local Buffer = buffer.create(SendSize)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
SendBuffer = Buffer SendBuffer = Buffer
end end
SendOffset = SendCursor SendOffset = SendCursor
SendCursor += Bytes SendCursor += Bytes
return SendOffset return SendOffset
end end
local function CreateQueue(): Queue local function CreateQueue(): Queue
return { return {
head = nil, head = nil,
tail = nil tail = nil,
} }
end end
local function Pop(queue: Queue): any local function Pop(queue: Queue): any
local head = queue.head local head = queue.head
if head == nil then if head == nil then
return return
end end
queue.head = head.next queue.head = head.next
return head.value return head.value
end end
local function Push(queue: Queue, value: any) local function Push(queue: Queue, value: any)
local entry: Entry = { local entry: Entry = {
value = value, value = value,
next = nil next = nil,
} }
if queue.tail ~= nil then if queue.tail ~= nil then
queue.tail.next = entry queue.tail.next = entry
end end
queue.tail = entry queue.tail = entry
if queue.head == nil then if queue.head == nil then
queue.head = entry queue.head = entry
end end
end end
local Types = {} local Types = {}
local Calls = table.create(256) local Calls = table.create(256)
local Events: any = { local Events: any = {
Reliable = table.create(256), Reliable = table.create(256),
Unreliable = table.create(256) Unreliable = table.create(256),
} }
local Queue: any = { local Queue: any = {
Reliable = table.create(256), Reliable = table.create(256),
Unreliable = table.create(256) Unreliable = table.create(256),
} }
function Types.ReadEVENT_UpdateTransform(): (number, CFrame) function Types.ReadEVENT_UpdateTransform(): (number, CFrame)
-- Read BLOCK: 32 bytes -- Read BLOCK: 32 bytes
local BLOCK_START = Read(32) local BLOCK_START = Read(32)
@ -232,52 +232,51 @@ function Types.WriteEVENT_SpawnMob(Value1: number, Value2: CFrame, Value3: numbe
buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3) buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3)
end end
local PlayersMap: { [Player]: BufferSave } = {}
local PlayersMap: {[Player]: BufferSave} = {}
Players.PlayerRemoving:Connect(function(Player) Players.PlayerRemoving:Connect(function(Player)
PlayersMap[Player] = nil PlayersMap[Player] = nil
end) end)
local function StepReplication() local function StepReplication()
for Player, Send in PlayersMap do for Player, Send in PlayersMap do
if Send.Cursor <= 0 then if Send.Cursor <= 0 then
continue continue
end end
local Buffer = buffer.create(Send.Cursor) local Buffer = buffer.create(Send.Cursor)
buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor) buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor)
Reliable:FireClient(Player, Buffer, Send.Instances) Reliable:FireClient(Player, Buffer, Send.Instances)
Send.Size = 64 Send.Size = 64
Send.Cursor = 0 Send.Cursor = 0
Send.Buffer = buffer.create(64) Send.Buffer = buffer.create(64)
table.clear(Send.Instances) table.clear(Send.Instances)
end end
end end
RunService.Heartbeat:Connect(StepReplication) RunService.Heartbeat:Connect(StepReplication)
Reliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: {Instance}) Reliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: { Instance })
RecieveCursor = 0 RecieveCursor = 0
RecieveBuffer = Buffer RecieveBuffer = Buffer
RecieveInstances = Instances RecieveInstances = Instances
RecieveInstanceCursor = 0 RecieveInstanceCursor = 0
local Size = buffer.len(RecieveBuffer) local Size = buffer.len(RecieveBuffer)
while (RecieveCursor < Size) do while RecieveCursor < Size do
-- Read BLOCK: 1 bytes -- Read BLOCK: 1 bytes
local BLOCK_START = Read(1) local BLOCK_START = Read(1)
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0) local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
end end
end) end)
Unreliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: {Instance}) Unreliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: { Instance })
RecieveCursor = 0 RecieveCursor = 0
RecieveBuffer = Buffer RecieveBuffer = Buffer
RecieveInstances = Instances RecieveInstances = Instances
RecieveInstanceCursor = 0 RecieveInstanceCursor = 0
local Size = buffer.len(RecieveBuffer) local Size = buffer.len(RecieveBuffer)
while (RecieveCursor < Size) do while RecieveCursor < Size do
-- Read BLOCK: 1 bytes -- Read BLOCK: 1 bytes
local BLOCK_START = Read(1) local BLOCK_START = Read(1)
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0) local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
@ -302,7 +301,7 @@ return {
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
Unreliable:FireAllClients(Buffer, SendInstances) Unreliable:FireAllClients(Buffer, SendInstances)
end, end,
FireList = function(List: {Player}, Value1: number, Value2: CFrame): () FireList = function(List: { Player }, Value1: number, Value2: CFrame): ()
Load() Load()
Types.WriteEVENT_UpdateTransform(Value1, Value2) Types.WriteEVENT_UpdateTransform(Value1, Value2)
local Buffer = buffer.create(SendCursor) local Buffer = buffer.create(SendCursor)
@ -342,7 +341,7 @@ return {
PlayersMap[Player] = Save() PlayersMap[Player] = Save()
end end
end, end,
FireList = function(List: {Player}, Value1: number, Value2: CFrame, Value3: number): () FireList = function(List: { Player }, Value1: number, Value2: CFrame, Value3: number): ()
Load() Load()
Types.WriteEVENT_SpawnMob(Value1, Value2, Value3) Types.WriteEVENT_SpawnMob(Value1, Value2, Value3)
local Buffer, Size, Instances = SendBuffer, SendCursor, SendInstances local Buffer, Size, Instances = SendBuffer, SendCursor, SendInstances
@ -370,5 +369,4 @@ return {
end end
end, end,
}, },
} }

View file

@ -1,33 +1,33 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService") local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jabby = require(ReplicatedStorage.Packages.jabby) local jabby = require(ReplicatedStorage.Packages.jabby)
local std = require(ReplicatedStorage.std) local std = require(ReplicatedStorage.std)
local Scheduler = std.Scheduler local Scheduler = std.Scheduler
local world = std.world local world = std.world
local function start(modules) local function start(modules)
local scheduler = Scheduler.new(world, require(ReplicatedStorage.std.components)) local scheduler = Scheduler.new(world, require(ReplicatedStorage.std.components))
for _, module in modules do for _, module in modules do
require(module)(scheduler) require(module)(scheduler)
end end
local events = scheduler.collect.all() local events = scheduler.collect.all()
scheduler.systems.begin(events) scheduler.systems.begin(events)
jabby.set_check_function(function(player) jabby.set_check_function(function(player)
return true return true
end) end)
if RunService:IsClient() then if RunService:IsClient() then
local client = jabby.obtain_client() local client = jabby.obtain_client()
local dtor local dtor
UserInputService.InputBegan:Connect(function(input) UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.F4 then if input.KeyCode == Enum.KeyCode.F4 then
if dtor then if dtor then
dtor() dtor()
end end
dtor = client.spawn_app(client.apps.home) dtor = client.spawn_app(client.apps.home)
end end
end) end)
end end
end end

View file

@ -8,33 +8,33 @@ local FAILURE = 1
local RUNNING = 2 local RUNNING = 2
local function SEQUENCE(nodes) local function SEQUENCE(nodes)
return function(...) return function(...)
for _, node in nodes do for _, node in nodes do
local status = node(...) local status = node(...)
if status == FAILURE or status == RUNNING then if status == FAILURE or status == RUNNING then
return status return status
end end
end end
return SUCCESS return SUCCESS
end end
end end
local function FALLBACK(nodes) local function FALLBACK(nodes)
return function(...) return function(...)
for _, node in nodes do for _, node in nodes do
local status = node(...) local status = node(...)
if status == SUCCESS or status == RUNNING then if status == SUCCESS or status == RUNNING then
return status return status
end end
end end
return FAILURE return FAILURE
end end
end end
local bt = { local bt = {
SEQUENCE = SEQUENCE, SEQUENCE = SEQUENCE,
FALLBACK = FALLBACK, FALLBACK = FALLBACK,
RUNNING = RUNNING RUNNING = RUNNING,
} }
return bt return bt

View file

@ -1,133 +1,139 @@
local jecs = require(game:GetService("ReplicatedStorage").ecs) local jecs = require(game:GetService("ReplicatedStorage").ecs)
type World = jecs.World type World = jecs.World
type Tracker<T> = { track: (world: World, fn: (changes: { type Tracker<T> = {
added: () -> () -> (number, T), track: (
removed: () -> () -> number, world: World,
changed: () -> () -> (number, T, T) fn: (
}) -> ()) -> () changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T),
}
) -> ()
) -> (),
} }
type Entity<T = any> = number & { __nominal_type_dont_use: T } type Entity<T = any> = number & { __nominal_type_dont_use: T }
local function diff(a, b) local function diff(a, b)
local size = 0 local size = 0
for k, v in a do for k, v in a do
if b[k] ~= v then if b[k] ~= v then
return true return true
end end
size += 1 size += 1
end end
for k, v in b do for k, v in b do
size -= 1 size -= 1
end end
if size ~= 0 then if size ~= 0 then
return true return true
end end
return false return false
end end
local function ChangeTracker<T>(world: World, T: Entity<T>): Tracker<T> local function ChangeTracker<T>(world: World, T: Entity<T>): Tracker<T>
local sparse = world.entityIndex.sparse local sparse = world.entityIndex.sparse
local PreviousT = jecs.pair(jecs.Rest, T) local PreviousT = jecs.pair(jecs.Rest, T)
local add = {} local add = {}
local added local added
local removed local removed
local is_trivial local is_trivial
local function changes_added() local function changes_added()
added = true added = true
local q = world:query(T):without(PreviousT):drain() local q = world:query(T):without(PreviousT):drain()
return function() return function()
local id, data = q.next() local id, data = q.next()
if not id then if not id then
return nil return nil
end end
is_trivial = typeof(data) ~= "table" is_trivial = typeof(data) ~= "table"
add[id] = data add[id] = data
return id, data return id, data
end end
end end
local function changes_changed() local function changes_changed()
local q = world:query(T, PreviousT):drain() local q = world:query(T, PreviousT):drain()
return function() return function()
local id, new, old = q.next() local id, new, old = q.next()
while true do while true do
if not id then if not id then
return nil return nil
end end
if not is_trivial then if not is_trivial then
if diff(new, old) then if diff(new, old) then
break break
end end
elseif new ~= old then elseif new ~= old then
break break
end end
id, new, old = q.next() id, new, old = q.next()
end end
local record = sparse[id] local record = sparse[id]
local archetype = record.archetype local archetype = record.archetype
local column = archetype.records[PreviousT].column local column = archetype.records[PreviousT].column
local data = if is_trivial then new else table.clone(new) local data = if is_trivial then new else table.clone(new)
archetype.columns[column][record.row] = data archetype.columns[column][record.row] = data
return id, old, new return id, old, new
end end
end end
local function changes_removed() local function changes_removed()
removed = true removed = true
local q = world:query(PreviousT):without(T):drain() local q = world:query(PreviousT):without(T):drain()
return function() return function()
local id = q.next() local id = q.next()
if id then if id then
world:remove(id, PreviousT) world:remove(id, PreviousT)
end end
return id return id
end end
end end
local changes = { local changes = {
added = changes_added, added = changes_added,
changed = changes_changed, changed = changes_changed,
removed = changes_removed, removed = changes_removed,
} }
local function track(fn) local function track(fn)
added = false added = false
removed = false removed = false
fn(changes) fn(changes)
if not added then if not added then
for _ in changes_added() do for _ in changes_added() do
end end
end end
if not removed then if not removed then
for _ in changes_removed() do for _ in changes_removed() do
end end
end end
for e, data in add do for e, data in add do
world:set(e, PreviousT, if is_trivial then data else table.clone(data)) world:set(e, PreviousT, if is_trivial then data else table.clone(data))
end end
end end
local tracker = { track = track } local tracker = { track = track }
return tracker return tracker
end end
return ChangeTracker return ChangeTracker

View file

@ -1,25 +1,26 @@
local jecs = require(game:GetService("ReplicatedStorage").ecs) local jecs = require(game:GetService("ReplicatedStorage").ecs)
local world = require(script.Parent.world) local world = require(script.Parent.world)
type Entity<T=nil> = jecs.Entity<T> type Entity<T = nil> = jecs.Entity<T>
local components: { local components: {
Character: Entity<Model>, Character: Entity<Model>,
Mob: Entity, Mob: Entity,
Model: Entity<Model>, Model: Entity<Model>,
Player: Entity, Player: Entity,
Target: Entity, Target: Entity,
Transform: Entity<CFrame>, Transform: Entity<CFrame>,
Velocity: Entity<number>, Velocity: Entity<number>,
Previous: Entity, Previous: Entity,
} = { } =
Character = world:component(), {
Mob = world:component(), Character = world:component(),
Model = world:component(), Mob = world:component(),
Player = world:component(), Model = world:component(),
Target = world:component(), Player = world:component(),
Transform = world:component(), Target = world:component(),
Velocity = world:component(), Transform = world:component(),
Previous = world:component(), Velocity = world:component(),
} Previous = world:component(),
}
return table.freeze(components) return table.freeze(components)

View file

@ -1,11 +1,11 @@
local world = require(script.Parent.world)
local handle = require(script.Parent.handle) local handle = require(script.Parent.handle)
local world = require(script.Parent.world)
local singleton = world:entity() local singleton = world:entity()
local function ctx() local function ctx()
-- Cannot cache handles because they will get invalidated -- Cannot cache handles because they will get invalidated
return handle(singleton) return handle(singleton)
end end
return ctx return ctx

View file

@ -2,55 +2,55 @@ local jecs = require(game:GetService("ReplicatedStorage").ecs)
local world = require(script.Parent.world) local world = require(script.Parent.world)
type Handle = { type Handle = {
has: (self: Handle, id: jecs.Entity) -> boolean, has: (self: Handle, id: jecs.Entity) -> boolean,
get: <T>(self: Handle, id: jecs.Entity<T>) -> T?, get: <T>(self: Handle, id: jecs.Entity<T>) -> T?,
add: <T>(self: Handle, id: jecs.Entity<T>) -> Handle, add: <T>(self: Handle, id: jecs.Entity<T>) -> Handle,
set: <T>(self: Handle, id: jecs.Entity<T>, value: T) -> Handle, set: <T>(self: Handle, id: jecs.Entity<T>, value: T) -> Handle,
id: (self: Handle?) -> jecs.Entity id: (self: Handle?) -> jecs.Entity,
} }
local handle: (e: jecs.Entity) -> Handle local handle: (e: jecs.Entity) -> Handle
do do
local e local e
local function has(_, id) local function has(_, id)
return world:has(e, id) return world:has(e, id)
end end
local function get(_, id) local function get(_, id)
return world:get(e, id) return world:get(e, id)
end end
local function set(self, id, value) local function set(self, id, value)
world:set(e, id, value) world:set(e, id, value)
return self return self
end end
local function add(self, id) local function add(self, id)
world:add(e, id) world:add(e, id)
return self return self
end end
local function clear(self) local function clear(self)
world:clear(e) world:clear(e)
return self return self
end end
local function delete(self) local function delete(self)
world:delete(e) world:delete(e)
end end
local function id() local function id()
return e return e
end end
local entity = { local entity = {
has = has, has = has,
get = get, get = get,
set = set, set = set,
add = add, add = add,
clear = clear, clear = clear,
id = id, id = id,
} }
function handle(id) function handle(id)
e = id e = id
return entity return entity
end end
end end
return handle return handle

View file

@ -4,29 +4,29 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.ecs) local jecs = require(ReplicatedStorage.ecs)
local function create_cache(hook) local function create_cache(hook)
local columns = setmetatable({}, { local columns = setmetatable({}, {
__index = function(self, component) __index = function(self, component)
local column = {} local column = {}
self[component] = column self[component] = column
return column return column
end end,
}) })
return function(world, component, fn) return function(world, component, fn)
local column = columns[component] local column = columns[component]
table.insert(column, fn) table.insert(column, fn)
world:set(component, hook, function(entity, value) world:set(component, hook, function(entity, value)
for _, callback in column do for _, callback in column do
callback(entity, value) callback(entity, value)
end end
end) end)
end end
end end
local hooks = { local hooks = {
OnSet = create_cache(jecs.OnSet), OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd), OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove) OnRemove = create_cache(jecs.OnRemove),
} }
return hooks return hooks

View file

@ -7,19 +7,19 @@ local Scheduler = require(script.scheduler)
export type Scheduler = Scheduler.Scheduler export type Scheduler = Scheduler.Scheduler
local std = { local std = {
ChangeTracker = require(script.changetracker), ChangeTracker = require(script.changetracker),
Scheduler = Scheduler, Scheduler = Scheduler,
bt = require(script.bt), bt = require(script.bt),
collect = require(script.collect), collect = require(script.collect),
components = require(script.components), components = require(script.components),
ctx = require(script.ctx), ctx = require(script.ctx),
handle = require(script.handle), handle = require(script.handle),
interval = require(script.interval), interval = require(script.interval),
ref = require(script.ref), ref = require(script.ref),
world = world :: World, world = world :: World,
pair = jecs.pair, pair = jecs.pair,
__ = jecs.w, __ = jecs.w,
hooks = require(script.hooks) hooks = require(script.hooks),
} }
return std return std

View file

@ -1,19 +1,19 @@
local function interval(s) local function interval(s)
local pin local pin
local function throttle() local function throttle()
if not pin then if not pin then
pin = os.clock() pin = os.clock()
end end
local elapsed = os.clock() - pin > s local elapsed = os.clock() - pin > s
if elapsed then if elapsed then
pin = os.clock() pin = os.clock()
end end
return elapsed return elapsed
end end
return throttle return throttle
end end
return interval return interval

View file

@ -1,24 +1,24 @@
local world = require(script.Parent.world)
local handle = require(script.Parent.handle) local handle = require(script.Parent.handle)
local world = require(script.Parent.world)
local refs = {} local refs = {}
local function fini(key) local function fini(key)
return function() return function()
refs[key] = nil refs[key] = nil
end end
end end
local function ref(key): (handle.Handle, (() -> ())?) local function ref(key): (handle.Handle, (() -> ())?)
if not key then if not key then
return handle(world:entity()) return handle(world:entity())
end end
local e = refs[key] local e = refs[key]
if not e then if not e then
e = world:entity() e = world:entity()
refs[key] = e refs[key] = e
end end
-- Cannot cache handles because they will get invalidated -- Cannot cache handles because they will get invalidated
return handle(e), fini(key) return handle(e), fini(key)
end end
return ref return ref

View file

@ -1,8 +1,8 @@
local reserved = 0 local reserved = 0
local function reserve() local function reserve()
reserved += 1 reserved += 1
return reserved return reserved
end end
-- If you don't like passing around a world singleton -- If you don't like passing around a world singleton
@ -19,13 +19,13 @@ end
world:set(e, components.Transform, CFrame) world:set(e, components.Transform, CFrame)
]] ]]
local function register(world) local function register(world)
for _ = 1, reserved do for _ = 1, reserved do
world:component() world:component()
end end
return world return world
end end
return { return {
reserve = reserve, reserve = reserve,
register = register, register = register,
} }

View file

@ -7,245 +7,250 @@ local pair = jecs.pair
local Name = jecs.Name local Name = jecs.Name
type World = jecs.World type World = jecs.World
type Entity<T=nil> = jecs.Entity<T> type Entity<T = nil> = jecs.Entity<T>
type System = { type System = {
callback: (world: World) -> (), callback: (world: World) -> (),
id: number, id: number,
} }
type Systems = { System } type Systems = { System }
type Events = { type Events = {
RenderStepped: Systems, RenderStepped: Systems,
Heartbeat: Systems Heartbeat: Systems,
} }
export type Scheduler = { export type Scheduler = {
components: { components: {
Disabled: Entity, Disabled: Entity,
System: Entity<System>, System: Entity<System>,
Phase: Entity, Phase: Entity,
DependsOn: Entity DependsOn: Entity,
}, },
collect: { collect: {
under_event: (event: Entity) -> Systems, under_event: (event: Entity) -> Systems,
all: () -> Events all: () -> Events,
}, },
systems: { systems: {
begin: (events: Events) -> { [Entity]: thread }, begin: (events: Events) -> { [Entity]: thread },
new: (callback: (dt: number) -> (), phase: Entity) -> Entity new: (callback: (dt: number) -> (), phase: Entity) -> Entity,
}, },
phases: { phases: {
RenderStepped: Entity, RenderStepped: Entity,
Heartbeat: Entity, Heartbeat: Entity,
}, },
phase: (after: Entity) -> Entity, phase: (after: Entity) -> Entity,
debugging: boolean, debugging: boolean,
} }
local scheduler_new: (w: World, components: { [string]: Entity }) -> Scheduler local scheduler_new: (w: World, components: { [string]: Entity }) -> Scheduler
do do
local world: World local world: World
local Disabled: Entity local Disabled: Entity
local System: Entity<System> local System: Entity<System>
local DependsOn: Entity local DependsOn: Entity
local Phase: Entity local Phase: Entity
local Event: Entity<RBXScriptSignal> local Event: Entity<RBXScriptSignal>
local scheduler local scheduler
local RenderStepped local RenderStepped
local Heartbeat local Heartbeat
local PreAnimation local PreAnimation
local PreSimulation local PreSimulation
local sys: System local sys: System
local dt local dt
local function run() local function run()
local id = sys.id local id = sys.id
local system_data = scheduler.system_data[id] local system_data = scheduler.system_data[id]
if system_data.paused then return end if system_data.paused then
return
end
scheduler:mark_system_frame_start(id) scheduler:mark_system_frame_start(id)
sys.callback(dt) sys.callback(dt)
scheduler:mark_system_frame_end(id) scheduler:mark_system_frame_end(id)
end end
local function panic(str) local function panic(str)
-- We don't want to interrupt the loop when we error -- We don't want to interrupt the loop when we error
task.spawn(error, str) task.spawn(error, str)
end end
local function begin(events: { Systems }) local function begin(events: { Systems })
local threads = {} local threads = {}
for event, systems in events do for event, systems in events do
if not event then continue end if not event then
local event_name = tostring(event) continue
threads[event] = task.spawn(function() end
while true do local event_name = tostring(event)
dt = event:Wait() threads[event] = task.spawn(function()
while true do
dt = event:Wait()
debug.profilebegin(event_name) debug.profilebegin(event_name)
for _, s in systems do for _, s in systems do
sys = s sys = s
local didNotYield, why = xpcall(function() local didNotYield, why = xpcall(function()
for _ in run do break end for _ in run do
end, debug.traceback) break
end
end, debug.traceback)
if didNotYield then if didNotYield then
continue continue
end end
if string.find(why, "thread is not yieldable") then if string.find(why, "thread is not yieldable") then
panic("Not allowed to yield in the systems." panic(
.. "\n" "Not allowed to yield in the systems."
.. "System: " .. "\n"
.. debug.info(s.callback, "n") .. "System: "
.. " has been ejected" .. debug.info(s.callback, "n")
) .. " has been ejected"
continue )
end continue
panic(why) end
end panic(why)
debug.profileend() end
end debug.profileend()
end) end
end end)
return threads end
end return threads
end
local function scheduler_collect_systems_under_phase_recursive(systems, phase) local function scheduler_collect_systems_under_phase_recursive(systems, phase)
local phase_name = world:get(phase, Name) local phase_name = world:get(phase, Name)
for _, s in world:query(System):with(pair(DependsOn, phase)) do for _, s in world:query(System):with(pair(DependsOn, phase)) do
table.insert(systems, { table.insert(systems, {
id = scheduler:register_system({ id = scheduler:register_system({
name = s.name, name = s.name,
phase = phase_name phase = phase_name,
}), }),
callback = s.callback callback = s.callback,
}) })
end end
for after in world:query(Phase):with(pair(DependsOn, phase)) do for after in world:query(Phase):with(pair(DependsOn, phase)) do
scheduler_collect_systems_under_phase_recursive(systems, after) scheduler_collect_systems_under_phase_recursive(systems, after)
end end
end end
local function scheduler_collect_systems_under_event(event) local function scheduler_collect_systems_under_event(event)
local systems = {} local systems = {}
scheduler_collect_systems_under_phase_recursive(systems, event) scheduler_collect_systems_under_phase_recursive(systems, event)
return systems return systems
end end
local function scheduler_collect_systems_all() local function scheduler_collect_systems_all()
local events = {} local events = {}
for phase, event in world:query(Event):with(Phase) do for phase, event in world:query(Event):with(Phase) do
events[event] = scheduler_collect_systems_under_event(phase) events[event] = scheduler_collect_systems_under_event(phase)
end end
return events return events
end end
local function scheduler_phase_new(after) local function scheduler_phase_new(after)
local phase = world:entity() local phase = world:entity()
world:add(phase, Phase) world:add(phase, Phase)
local dependency = pair(DependsOn, after) local dependency = pair(DependsOn, after)
world:add(phase, dependency) world:add(phase, dependency)
return phase return phase
end end
local function scheduler_systems_new(callback, phase) local function scheduler_systems_new(callback, phase)
local system = world:entity() local system = world:entity()
local name = debug.info(callback, "n") local name = debug.info(callback, "n")
world:set(system, System, { callback = callback, name = name }) world:set(system, System, { callback = callback, name = name })
world:add(system, pair(DependsOn, phase)) world:add(system, pair(DependsOn, phase))
return system return system
end end
function scheduler_new(w: World, components: { [string]: Entity }) function scheduler_new(w: World, components: { [string]: Entity })
world = w world = w
Disabled = world:component() Disabled = world:component()
System = world:component() System = world:component()
Phase = world:component() Phase = world:component()
DependsOn = world:component() DependsOn = world:component()
Event = world:component() Event = world:component()
RenderStepped = world:component() RenderStepped = world:component()
Heartbeat = world:component() Heartbeat = world:component()
PreSimulation = world:component() PreSimulation = world:component()
PreAnimation = world:component() PreAnimation = world:component()
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
if RunService:IsClient() then if RunService:IsClient() then
world:add(RenderStepped, Phase) world:add(RenderStepped, Phase)
world:set(RenderStepped, Event, RunService.RenderStepped) world:set(RenderStepped, Event, RunService.RenderStepped)
end end
world:add(Heartbeat, Phase) world:add(Heartbeat, Phase)
world:set(Heartbeat, Event, RunService.Heartbeat) world:set(Heartbeat, Event, RunService.Heartbeat)
world:add(PreSimulation, Phase) world:add(PreSimulation, Phase)
world:set(PreSimulation, Event, RunService.PreSimulation) world:set(PreSimulation, Event, RunService.PreSimulation)
world:add(PreAnimation, Phase) world:add(PreAnimation, Phase)
world:set(PreAnimation, Event, RunService.PreAnimation) world:set(PreAnimation, Event, RunService.PreAnimation)
for name, component in components do for name, component in components do
world:set(component, Name, name) world:set(component, Name, name)
end end
table.insert(jabby.public, { table.insert(jabby.public, {
class_name = "World", class_name = "World",
name = "MyWorld", name = "MyWorld",
world = world, world = world,
debug = Name, debug = Name,
entities = {} entities = {},
}) })
jabby.public.updated = true
scheduler = jabby.scheduler.create("scheduler")
jabby.public.updated = true table.insert(jabby.public, scheduler)
scheduler = jabby.scheduler.create("scheduler")
table.insert(jabby.public, scheduler) return {
phase = scheduler_phase_new,
return { phases = {
phase = scheduler_phase_new, RenderStepped = RenderStepped,
PreSimulation = PreSimulation,
Heartbeat = Heartbeat,
PreAnimation = PreAnimation,
},
phases = { world = world,
RenderStepped = RenderStepped,
PreSimulation = PreSimulation,
Heartbeat = Heartbeat,
PreAnimation = PreAnimation
},
world = world, components = {
DependsOn = DependsOn,
Disabled = Disabled,
Phase = Phase,
System = System,
},
components = { collect = {
DependsOn = DependsOn, under_event = scheduler_collect_systems_under_event,
Disabled = Disabled, all = scheduler_collect_systems_all,
Phase = Phase, },
System = System,
},
collect = { systems = {
under_event = scheduler_collect_systems_under_event, new = scheduler_systems_new,
all = scheduler_collect_systems_all begin = begin,
}, },
}
systems = { end
new = scheduler_systems_new,
begin = begin
}
}
end
end end
return { return {
new = scheduler_new new = scheduler_new,
} }

View file

@ -21,58 +21,53 @@ local Player = cts.Player
local Character = cts.Character local Character = cts.Character
local function mobsMove(dt: number) local function mobsMove(dt: number)
local targets = {} local targets = {}
for _, character in world:query(Character):with(Player):iter() do for _, character in world:query(Character):with(Player):iter() do
table.insert(targets, (character.PrimaryPart :: Part).Position) table.insert(targets, (character.PrimaryPart :: Part).Position)
end end
for mob, transform, v in world:query(Transform, Velocity):with(Mob):iter() do for mob, transform, v in world:query(Transform, Velocity):with(Mob):iter() do
local cf = transform.new local cf = transform.new
local p = cf.Position local p = cf.Position
local target local target
local closest local closest
for _, pos in targets do for _, pos in targets do
local distance = (p - pos).Magnitude local distance = (p - pos).Magnitude
if not target or distance < closest then if not target or distance < closest then
target = pos target = pos
closest = distance closest = distance
end end
end end
if not target then if not target then
continue continue
end end
local moving = CFrame.new(p + (target - p).Unit * dt * v) local moving = CFrame.new(p + (target - p).Unit * dt * v)
transform.new = moving transform.new = moving
blink.UpdateTransform.FireAll(mob, moving) blink.UpdateTransform.FireAll(mob, moving)
end end
end end
local throttle = interval(5) local throttle = interval(5)
local function spawnMobs() local function spawnMobs()
if throttle() then if throttle() then
local p = Vector3.new(0, 5, 0) local p = Vector3.new(0, 5, 0)
local cf = CFrame.new(p) local cf = CFrame.new(p)
local v = 5 local v = 5
local id = ref() local id = ref():set(Velocity, v):set(Transform, { new = cf }):add(Mob).id()
:set(Velocity, v)
:set(Transform, { new = cf })
:add(Mob)
.id()
blink.SpawnMob.FireAll(id, cf, v) blink.SpawnMob.FireAll(id, cf, v)
end end
end end
return function(scheduler: std.Scheduler) return function(scheduler: std.Scheduler)
local phases = scheduler.phases local phases = scheduler.phases
local system_new = scheduler.systems.new local system_new = scheduler.systems.new
system_new(mobsMove, phases.Heartbeat) system_new(mobsMove, phases.Heartbeat)
system_new(spawnMobs, phases.Heartbeat) system_new(spawnMobs, phases.Heartbeat)
end end

View file

@ -16,31 +16,28 @@ local world: std.World = std.world
local conn = {} local conn = {}
local function players() local function players()
for _, player in playersAdded do for _, player in playersAdded do
world:set( world:set(std.world:entity(), cts.Transform)
std.world:entity(),
cts.Transform
)
local e = ref(player.UserId):set(Player, player) local e = ref(player.UserId):set(Player, player)
local characterAdd = player.CharacterAdded local characterAdd = player.CharacterAdded
conn[e.id()] = characterAdd:Connect(function(rig) conn[e.id()] = characterAdd:Connect(function(rig)
while rig.Parent ~= workspace do while rig.Parent ~= workspace do
task.wait() task.wait()
end end
e:set(Character, rig) e:set(Character, rig)
end) end)
end end
for _, player in playersRemoved do for _, player in playersRemoved do
local id = ref(player.UserId):clear().id() local id = ref(player.UserId):clear().id()
conn[id]:Disconnect() conn[id]:Disconnect()
conn[id] = nil conn[id] = nil
end end
end end
return function(scheduler: std.Scheduler) return function(scheduler: std.Scheduler)
local phases = scheduler.phases local phases = scheduler.phases
local system_new = scheduler.systems.new local system_new = scheduler.systems.new
system_new(players, phases.Heartbeat) system_new(players, phases.Heartbeat)
end end

View file

@ -10,30 +10,30 @@ local Model = cts.Model
local Transform = cts.Transform local Transform = cts.Transform
local function move(dt: number) local function move(dt: number)
for _, transform, model in world:query(Transform, Model):iter() do for _, transform, model in world:query(Transform, Model):iter() do
local cf = transform.new local cf = transform.new
if cf ~= transform.old then if cf ~= transform.old then
local origo = model.PrimaryPart.CFrame local origo = model.PrimaryPart.CFrame
model.PrimaryPart.CFrame = origo:Lerp(cf, 1) model.PrimaryPart.CFrame = origo:Lerp(cf, 1)
transform.old = cf transform.old = cf
end end
end end
end end
local function syncTransforms() local function syncTransforms()
for _, id, cf in blink.UpdateTransform.Iter() do for _, id, cf in blink.UpdateTransform.Iter() do
local e = ref("server-"..id) local e = ref("server-" .. id)
local transform = e:get(cts.Transform) local transform = e:get(cts.Transform)
if not transform then if not transform then
continue continue
end end
transform.new = cf transform.new = cf
end end
end end
return function(scheduler: std.Scheduler) return function(scheduler: std.Scheduler)
local phases = scheduler.phases local phases = scheduler.phases
local system_new = scheduler.systems.new local system_new = scheduler.systems.new
system_new(move, phases.Heartbeat) system_new(move, phases.Heartbeat)
system_new(syncTransforms, phases.RenderStepped) system_new(syncTransforms, phases.RenderStepped)
end end

View file

@ -6,26 +6,26 @@ local world = std.world
local cts = std.components local cts = std.components
local function syncMobs() local function syncMobs()
for _, id, cf, vel in blink.SpawnMob.Iter() do for _, id, cf, vel in blink.SpawnMob.Iter() do
local part = Instance.new("Part") local part = Instance.new("Part")
part.Size = Vector3.one * 5 part.Size = Vector3.one * 5
part.BrickColor = BrickColor.Red() part.BrickColor = BrickColor.Red()
part.Anchored = true part.Anchored = true
local model = Instance.new("Model") local model = Instance.new("Model")
model.PrimaryPart = part model.PrimaryPart = part
part.Parent = model part.Parent = model
model.Parent = workspace model.Parent = workspace
ref("server-"..id) ref("server-" .. id)
:set(cts.Transform, { new = cf, old = cf }) :set(cts.Transform, { new = cf, old = cf })
:set(cts.Velocity, vel) :set(cts.Velocity, vel)
:set(cts.Model, model) :set(cts.Model, model)
:add(cts.Mob) :add(cts.Mob)
end end
end end
return function(scheduler: std.Scheduler) return function(scheduler: std.Scheduler)
local phases = scheduler.phases local phases = scheduler.phases
local system_new = scheduler.systems.new local system_new = scheduler.systems.new
system_new(syncMobs, phases.RenderStepped) system_new(syncMobs, phases.RenderStepped)
end end

View file

@ -23,20 +23,18 @@ 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, Vector3.new(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, Vector3.new(10, 20, 30)) world:set(alice, Position, Vector3.new(10, 20, 30))
world:add(alice, Walking) world:add(alice, Walking)
-- Remove tag -- Remove tag
world:remove(alice, Walking) 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(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`) print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
end end
-- Output: -- Output:

View file

@ -11,67 +11,55 @@ local Moon = world:component()
local Vector3 local Vector3
do do
Vector3 = {} Vector3 = {}
Vector3.__index = Vector3 Vector3.__index = Vector3
function Vector3.new(x, y, z) function Vector3.new(x, y, z)
x = x or 0 x = x or 0
y = y or 0 y = y or 0
z = z or 0 z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3) return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end end
function Vector3.__add(left, right) function Vector3.__add(left, right)
return Vector3.new( return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
left.X + right.X, end
left.Y + right.Y,
left.Z + right.Z
)
end
function Vector3.__mul(left, right) function Vector3.__mul(left, right)
if typeof(right) == "number" then if typeof(right) == "number" then
return Vector3.new( return Vector3.new(left.X * right, left.Y * right, left.Z * right)
left.X * right, end
left.Y * right, return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
left.Z * right end
)
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.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new() Vector3.zero = Vector3.new()
end end
local function path(entity) local function path(entity)
local str = world:get(entity, Name) local str = world:get(entity, Name)
local parent local parent
while true do while true do
parent = world:parent(entity) parent = world:parent(entity)
if not parent then if not parent then
break break
end end
entity = parent entity = parent
str = world:get(parent, Name) .. "/" .. str str = world:get(parent, Name) .. "/" .. str
end end
return str return str
end end
local function iterate(entity, parent) local function iterate(entity, parent)
local p = world:get(entity, Position) local p = world:get(entity, Position)
local actual = p + parent local actual = p + parent
print(path(entity)) print(path(entity))
print(`\{{actual.X}, {actual.Y}, {actual.Z}}`) print(`\{{actual.X}, {actual.Y}, {actual.Z}}`)
for child in world:query(pair(ChildOf, entity)) do for child in world:query(pair(ChildOf, entity)) do
--print(world:get(child, Name)) --print(world:get(child, Name))
iterate(child, actual) iterate(child, actual)
end end
end end
local sun = world:entity() local sun = world:entity()
@ -79,36 +67,35 @@ world:add(sun, Star)
world:set(sun, Position, Vector3.one) world:set(sun, Position, Vector3.one)
world:set(sun, Name, "Sun") world:set(sun, Name, "Sun")
do do
local earth = world:entity() local earth = world:entity()
world:set(earth, Name, "Earth") world:set(earth, Name, "Earth")
world:add(earth, pair(ChildOf, sun)) world:add(earth, pair(ChildOf, sun))
world:add(earth, Planet) world:add(earth, Planet)
world:set(earth, Position, Vector3.one * 3) world:set(earth, Position, Vector3.one * 3)
do do
local moon = world:entity() local moon = world:entity()
world:set(moon, Name, "Moon") world:set(moon, Name, "Moon")
world:add(moon, pair(ChildOf, earth)) world:add(moon, pair(ChildOf, earth))
world:add(moon, Moon) world:add(moon, Moon)
world:set(moon, Position, Vector3.one * 0.1) world:set(moon, Position, Vector3.one * 0.1)
print(`Child of Earth? {world:has(moon, pair(ChildOf, earth))}`) print(`Child of Earth? {world:has(moon, pair(ChildOf, earth))}`)
end end
local venus = world:entity() local venus = world:entity()
world:set(venus, Name, "Venus") world:set(venus, Name, "Venus")
world:add(venus, pair(ChildOf, sun)) world:add(venus, pair(ChildOf, sun))
world:add(venus, Planet) world:add(venus, Planet)
world:set(venus, Position, Vector3.one * 2) world:set(venus, Position, Vector3.one * 2)
local mercury = world:entity() local mercury = world:entity()
world:set(mercury, Name, "Mercury") world:set(mercury, Name, "Mercury")
world:add(mercury, pair(ChildOf, sun)) world:add(mercury, pair(ChildOf, sun))
world:add(mercury, Planet) world:add(mercury, Planet)
world:set(mercury, Position, Vector3.one) world:set(mercury, Position, Vector3.one)
iterate(sun, Vector3.zero)
iterate(sun, Vector3.zero)
end end
-- Output: -- Output:

View file

@ -6,16 +6,16 @@ local Model = world:component()
-- It is important to define hooks for the component before the component is ever used -- It is important to define hooks for the component before the component is ever used
-- otherwise the hooks will never invoke! -- otherwise the hooks will never invoke!
world:set(Model, jecs.OnRemove, function(entity) world:set(Model, jecs.OnRemove, function(entity)
-- OnRemove is invoked before the component and its value is removed -- OnRemove is invoked before the component and its value is removed
-- which provides a stable reference to the entity at deletion. -- which provides a stable reference to the entity at deletion.
-- This means that it is safe to retrieve the data inside of a hook -- This means that it is safe to retrieve the data inside of a hook
local model = world:get(entity, Model) local model = world:get(entity, Model)
model:Destroy() model:Destroy()
end) end)
world:set(Model, jecs.OnSet, function(entity, model) world:set(Model, jecs.OnSet, function(entity, model)
-- OnSet is invoked after the data has been assigned. -- OnSet is invoked after the data has been assigned.
-- It also returns the data for faster access. -- It also returns the data for faster access.
-- There may be some logic to do some side effects on reassignments -- There may be some logic to do some side effects on reassignments
model:SetAttribute("entityId", entity) model:SetAttribute("entityId", entity)
end) end)

View file

@ -7,41 +7,29 @@ local Name = world:component()
local Vector3 local Vector3
do do
Vector3 = {} Vector3 = {}
Vector3.__index = Vector3 Vector3.__index = Vector3
function Vector3.new(x, y, z) function Vector3.new(x, y, z)
x = x or 0 x = x or 0
y = y or 0 y = y or 0
z = z or 0 z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3) return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end end
function Vector3.__add(left, right) function Vector3.__add(left, right)
return Vector3.new( return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
left.X + right.X, end
left.Y + right.Y,
left.Z + right.Z
)
end
function Vector3.__mul(left, right) function Vector3.__mul(left, right)
if typeof(right) == "number" then if typeof(right) == "number" then
return Vector3.new( return Vector3.new(left.X * right, left.Y * right, left.Z * right)
left.X * right, end
left.Y * right, return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
left.Z * right end
)
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.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new() Vector3.zero = Vector3.new()
end end
-- Create a few test entities for a Position, Velocity query -- Create a few test entities for a Position, Velocity query
@ -62,12 +50,12 @@ world:set(e3, Position, Vector3.new(10, 20, 30))
-- Create an uncached query for Position, Velocity. -- Create an uncached query for Position, Velocity.
for entity, p, v in world:query(Position, Velocity) do for entity, p, v in world:query(Position, Velocity) do
-- Iterate entities matching the query -- Iterate entities matching the query
p.X += v.X p.X += v.X
p.Y += v.Y p.Y += v.Y
p.Z += v.Z p.Z += v.Z
print(`{world:get(entity, Name)}: \{{p.X}, {p.Y}, {p.Z}}`) print(`{world:get(entity, Name)}: \{{p.X}, {p.Y}, {p.Z}}`)
end end
-- Output: -- Output:

View file

@ -2,179 +2,173 @@ local jecs = require("@jecs")
type World = jecs.WorldShim type World = jecs.WorldShim
type Tracker<T> = { track: (world: World, fn: (changes: { type Tracker<T> = {
added: () -> () -> (number, T), track: (
removed: () -> () -> number, world: World,
changed: () -> () -> (number, T, T) fn: (
}) -> ()) -> () changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T),
}
) -> ()
) -> (),
} }
local function diff(a, b) local function diff(a, b)
local size = 0 local size = 0
for k, v in a do for k, v in a do
if b[k] ~= v then if b[k] ~= v then
return true return true
end end
size += 1 size += 1
end end
for k, v in b do for k, v in b do
size -= 1 size -= 1
end end
if size ~= 0 then if size ~= 0 then
return true return true
end end
return false return false
end end
type Entity<T> = number & { __nominal_type_dont_use: T } type Entity<T> = number & { __nominal_type_dont_use: T }
local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T> local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
local PreviousT = jecs.pair(jecs.Rest, T) local PreviousT = jecs.pair(jecs.Rest, T)
local add = {} local add = {}
local added local added
local removed local removed
local is_trivial local is_trivial
local function changes_added() local function changes_added()
added = true added = true
local q = world:query(T):without(PreviousT):drain() local q = world:query(T):without(PreviousT):drain()
return function() return function()
local id, data = q.next() local id, data = q.next()
if not id then if not id then
return nil return nil
end end
is_trivial = typeof(data) ~= "table" is_trivial = typeof(data) ~= "table"
add[id] = data add[id] = data
return id, data return id, data
end end
end end
local function changes_changed() local function changes_changed()
local q = world:query(T, PreviousT):drain() local q = world:query(T, PreviousT):drain()
return function() return function()
local id, new, old = q.next() local id, new, old = q.next()
while true do while true do
if not id then if not id then
return nil return nil
end end
if not is_trivial then if not is_trivial then
if diff(new, old) then if diff(new, old) then
break break
end end
elseif new ~= old then elseif new ~= old then
break break
end end
id, new, old = q.next() id, new, old = q.next()
end end
add[id] = new add[id] = new
return id, old, new return id, old, new
end end
end end
local function changes_removed() local function changes_removed()
removed = true removed = true
local q = world:query(PreviousT):without(T):drain() local q = world:query(PreviousT):without(T):drain()
return function() return function()
local id = q.next() local id = q.next()
if id then if id then
world:remove(id, PreviousT) world:remove(id, PreviousT)
end end
return id return id
end end
end end
local changes = { local changes = {
added = changes_added, added = changes_added,
changed = changes_changed, changed = changes_changed,
removed = changes_removed, removed = changes_removed,
} }
local function track(fn) local function track(fn)
added = false added = false
removed = false removed = false
fn(changes) fn(changes)
if not added then if not added then
for _ in changes_added() do for _ in changes_added() do
end end
end end
if not removed then if not removed then
for _ in changes_removed() do for _ in changes_removed() do
end end
end end
for e, data in add do for e, data in add do
world:set(e, PreviousT, if is_trivial then data else table.clone(data)) world:set(e, PreviousT, if is_trivial then data else table.clone(data))
end end
end end
local tracker = { track = track } local tracker = { track = track }
return tracker return tracker
end end
local Vector3 local Vector3
do do
Vector3 = {} Vector3 = {}
Vector3.__index = Vector3 Vector3.__index = Vector3
function Vector3.new(x, y, z) function Vector3.new(x, y, z)
x = x or 0 x = x or 0
y = y or 0 y = y or 0
z = z or 0 z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3) return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end end
function Vector3.__add(left, right) function Vector3.__add(left, right)
return Vector3.new( return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
left.X + right.X, end
left.Y + right.Y,
left.Z + right.Z
)
end
function Vector3.__mul(left, right) function Vector3.__mul(left, right)
if typeof(right) == "number" then if typeof(right) == "number" then
return Vector3.new( return Vector3.new(left.X * right, left.Y * right, left.Z * right)
left.X * right, end
left.Y * right, return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
left.Z * right end
)
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.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new() Vector3.zero = Vector3.new()
end end
local world = jecs.World.new() local world = jecs.World.new()
local Name = world:component() local Name = world:component()
local function named(ctr, name) local function named(ctr, name)
local e = ctr(world) local e = ctr(world)
world:set(e, Name, name) world:set(e, Name, name)
return e return e
end end
local function name(e) local function name(e)
return world:get(e, Name) return world:get(e, Name)
end end
local Position = named(world.component, "Position") local Position = named(world.component, "Position")
@ -189,50 +183,50 @@ local e2 = named(world.entity, "e2")
world:set(e2, Position, Vector3.new(10, 20, 30)) world:set(e2, Position, Vector3.new(10, 20, 30))
PositionTracker.track(function(changes) PositionTracker.track(function(changes)
-- You can iterate over different types of changes: Added, Changed, Removed -- You can iterate over different types of changes: Added, Changed, Removed
-- added queries for every entity with a new Position component -- added queries for every entity with a new Position component
for e, p in changes.added() do for e, p in changes.added() do
print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`) print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
end end
-- changed queries for entities who's changed their data since -- changed queries for entities who's changed their data since
-- last was it tracked -- last was it tracked
for _ in changes.changed() do for _ in changes.changed() do
print([[This won't print because it is the first time print([[This won't print because it is the first time
we are tracking the Position component]]) we are tracking the Position component]])
end end
-- removed queries for entities who's removed their Position component -- removed queries for entities who's removed their Position component
-- since last it was tracked -- since last it was tracked
for _ in changes.removed() do for _ in changes.removed() do
print([[This won't print because it is the first time print([[This won't print because it is the first time
we are tracking the Position component]]) we are tracking the Position component]])
end end
end) end)
world:set(e1, Position, Vector3.new(1, 1, 2) * 999) world:set(e1, Position, Vector3.new(1, 1, 2) * 999)
PositionTracker.track(function(changes) PositionTracker.track(function(changes)
for e, p in changes.added() do for e, p in changes.added() do
print([[This won't never print no Position component was added print([[This won't never print no Position component was added
since last time we tracked]]) since last time we tracked]])
end end
for e, old, new in changes.changed() do 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}\}`) print(`{name(e)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
end end
-- If you don't call e.g. changes.removed() then it will automatically drain its iterator and stage their changes. -- 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. -- This ensures you will not have any off-by-one frame errors.
end) end)
world:remove(e2, Position) world:remove(e2, Position)
PositionTracker.track(function(changes) PositionTracker.track(function(changes)
for e in changes.removed() do for e in changes.removed() do
print(`Position was removed from {name(e)}`) print(`Position was removed from {name(e)}`)
end end
end) end)
-- Output: -- Output:

View file

@ -5,108 +5,104 @@ local __ = jecs.Wildcard
local Name = jecs.Name local Name = jecs.Name
local world = jecs.World.new() local world = jecs.World.new()
type Id<T=nil> = number & {__T: T} type Id<T = nil> = number & { __T: T }
local Voxel = world:component() :: Id local Voxel = world:component() :: Id
local Position = world:component() :: Id<Vector3> local Position = world:component() :: Id<Vector3>
local Perception = world:component() :: Id<{ local Perception = world:component() :: Id<{
range: number, range: number,
fov: number, fov: number,
dir: Vector3 dir: Vector3,
}> }>
local PrimaryPart = world:component() :: Id<Part> local PrimaryPart = world:component() :: Id<Part>
local local_player = game:GetService("Players").LocalPlayer local local_player = game:GetService("Players").LocalPlayer
local function distance(a: Vector3, b: Vector3) local function distance(a: Vector3, b: Vector3)
return (b - a).Magnitude return (b - a).Magnitude
end end
local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, 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 = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit 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 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.X)) 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)
if angle_difference > 180 then if angle_difference > 180 then
angle_difference = 360 - angle_difference angle_difference = 360 - angle_difference
end end
return angle_difference <= (fov_angle / 2) return angle_difference <= (fov_angle / 2)
end end
local map = {} local map = {}
local grid = 50 local grid = 50
local function add_to_voxel(source: number, position: Vector3, local function add_to_voxel(source: number, position: Vector3, prev_voxel_id: number?)
prev_voxel_id: number?) local hash = position // grid
local voxel_id = map[hash]
local hash = position // grid if not voxel_id then
local voxel_id = map[hash] voxel_id = world:entity()
if not voxel_id then world:add(voxel_id, Voxel)
voxel_id = world:entity() world:set(voxel_id, Position, hash)
world:add(voxel_id, Voxel) map[hash] = voxel_id
world:set(voxel_id, Position, hash) end
map[hash] = voxel_id if prev_voxel_id ~= nil then
end world:remove(source, pair(ChildOf, prev_voxel_id))
if prev_voxel_id ~= nil then end
world:remove(source, pair(ChildOf, prev_voxel_id)) world:add(source, pair(ChildOf, voxel_id))
end
world:add(source, pair(ChildOf, voxel_id))
end end
local function reconcile_client_owned_assembly_to_voxel(dt: number) local function reconcile_client_owned_assembly_to_voxel(dt: number)
for e, part, position in world:query(PrimaryPart, Position) do for e, part, position in world:query(PrimaryPart, Position) do
local p = part.Position local p = part.Position
if p ~= position then if p ~= position then
world:set(e, Position, p) world:set(e, Position, p)
local voxel_id = world:target(e, ChildOf, 0) local voxel_id = world:target(e, ChildOf, 0)
if map[p // grid] == voxel_id then if map[p // grid] == voxel_id then
continue continue
end end
add_to_voxel(e, p, voxel_id) add_to_voxel(e, p, voxel_id)
end end
end end
end end
local function update_camera_direction(dt: number) local function update_camera_direction(dt: number)
for _, perception in world:query(Perception) do for _, perception in world:query(Perception) do
perception.dir = workspace.CurrentCamera.CFrame.LookVector perception.dir = workspace.CurrentCamera.CFrame.LookVector
end end
end end
local function perceive_enemies(dt: number) local function perceive_enemies(dt: number)
local it = world:query(Perception, Position, PrimaryPart) 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()
local voxel_id = map[self_primary_part.Position // grid] local voxel_id = map[self_primary_part.Position // grid]
local nearby_entities_query = world:query(Position, pair(ChildOf, voxel_id)) local nearby_entities_query = world:query(Position, pair(ChildOf, voxel_id))
for enemy, target_position in nearby_entities_query do for enemy, target_position in nearby_entities_query do
if distance(self_position, target_position) > self_perception.range then if distance(self_position, target_position) > self_perception.range then
continue continue
end end
if is_in_fov(self_position, target_position, if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then
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})`)
local p = target_position end
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
local player = world:entity() local player = world:entity()
world:set(player, Perception, { world:set(player, Perception, {
range = 100, range = 100,
fov = 90, fov = 90,
dir = Vector3.new(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 :: Model).PrimaryPart :: Part local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
@ -117,11 +113,10 @@ local enemy = world:entity()
world:set(enemy, Name, "Enemy $1") world:set(enemy, Name, "Enemy $1")
world:set(enemy, Position, Vector3.new(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, world) 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)
update_camera_direction(dt) update_camera_direction(dt)
perceive_enemies(dt) perceive_enemies(dt)

View file

@ -4,12 +4,12 @@ local world = jecs.World.new()
local Name = world:component() local Name = world:component()
local function named(ctr, name) local function named(ctr, name)
local e = ctr(world) local e = ctr(world)
world:set(e, Name, name) world:set(e, Name, name)
return e return e
end end
local function name(e) local function name(e)
return world:get(e, Name) return world:get(e, Name)
end end
local Eats = world:component() local Eats = world:component()
@ -27,9 +27,9 @@ local __ = jecs.Wildcard
-- Create a query that matches edible components -- Create a query that matches edible components
for entity, amount in world:query(pair(Eats, __)) do for entity, amount in world:query(pair(Eats, __)) do
-- Iterate the query -- Iterate the query
local food = world:target(entity, Eats) local food = world:target(entity, Eats)
print(`{name(entity)} eats {amount} {name(food)}`) print(`{name(entity)} eats {amount} {name(food)}`)
end end
-- Output: -- Output:

View file

@ -6,10 +6,10 @@
type i53 = number type i53 = number
type i24 = number type i24 = number
type Ty = {i53} type Ty = { i53 }
type ArchetypeId = number type ArchetypeId = number
type Column = {any} type Column = { any }
type Archetype = { type Archetype = {
id: number, id: number,
@ -21,8 +21,8 @@ type Archetype = {
}, },
types: Ty, types: Ty,
type: string | number, type: string | number,
entities: {number}, entities: { number },
columns: {Column}, columns: { Column },
records: {}, records: {},
} }
@ -31,12 +31,12 @@ type Record = {
row: number, row: number,
} }
type EntityIndex = {[i24]: Record} type EntityIndex = { [i24]: Record }
type ComponentIndex = {[i24]: ArchetypeMap} type ComponentIndex = { [i24]: ArchetypeMap }
type ArchetypeRecord = number type ArchetypeRecord = number
type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number} type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord }, size: number }
type Archetypes = {[ArchetypeId]: Archetype} type Archetypes = { [ArchetypeId]: Archetype }
type ArchetypeDiff = { type ArchetypeDiff = {
added: Ty, added: Ty,
@ -134,7 +134,7 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet
local archetypesMap = componentIndex[destinationId] local archetypesMap = componentIndex[destinationId]
if not archetypesMap then if not archetypesMap then
archetypesMap = {size = 0, sparse = {}} archetypesMap = { size = 0, sparse = {} }
componentIndex[destinationId] = archetypesMap componentIndex[destinationId] = archetypesMap
end end
@ -143,27 +143,27 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet
end end
end end
local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Archetype
local ty = hash(types) local ty = hash(types)
local id = world.nextArchetypeId + 1 local id = world.nextArchetypeId + 1
world.nextArchetypeId = id world.nextArchetypeId = id
local length = #types local length = #types
local columns = table.create(length) :: {any} local columns = table.create(length) :: { any }
for index in types do for index in types do
columns[index] = {} columns[index] = {}
end end
local archetype = { local archetype = {
columns = columns; columns = columns,
edges = {}; edges = {},
entities = {}; entities = {},
id = id; id = id,
records = {}; records = {},
type = ty; type = ty,
types = types; types = types,
} }
world.archetypeIndex[ty] = archetype world.archetypeIndex[ty] = archetype
world.archetypes[id] = archetype world.archetypes[id] = archetype
@ -178,17 +178,17 @@ local World = {}
World.__index = World World.__index = World
function World.new() function World.new()
local self = setmetatable({ local self = setmetatable({
archetypeIndex = {}; archetypeIndex = {},
archetypes = {}; archetypes = {},
componentIndex = {}; componentIndex = {},
entityIndex = {}; entityIndex = {},
hooks = { hooks = {
[ON_ADD] = {}; [ON_ADD] = {},
}; },
nextArchetypeId = 0; nextArchetypeId = 0,
nextComponentId = 0; nextComponentId = 0,
nextEntityId = 0; nextEntityId = 0,
ROOT_ARCHETYPE = (nil :: any) :: Archetype; ROOT_ARCHETYPE = (nil :: any) :: Archetype,
}, World) }, World)
return self return self
end end
@ -197,21 +197,21 @@ local function emit(world, eventDescription)
local event = eventDescription.event local event = eventDescription.event
table.insert(world.hooks[event], { table.insert(world.hooks[event], {
archetype = eventDescription.archetype; archetype = eventDescription.archetype,
ids = eventDescription.ids; ids = eventDescription.ids,
offset = eventDescription.offset; offset = eventDescription.offset,
otherArchetype = eventDescription.otherArchetype; otherArchetype = eventDescription.otherArchetype,
}) })
end end
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty) local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
if #added > 0 then if #added > 0 then
emit(world, { emit(world, {
archetype = archetype; archetype = archetype,
event = ON_ADD; event = ON_ADD,
ids = added; ids = added,
offset = row; offset = row,
otherArchetype = otherArchetype; otherArchetype = otherArchetype,
}) })
end end
end end
@ -232,7 +232,7 @@ local function ensureArchetype(world: World, types, prev)
return archetypeOf(world, types, prev) return archetypeOf(world, types, prev)
end end
local function findInsert(types: {i53}, toAdd: i53) local function findInsert(types: { i53 }, toAdd: i53)
for i, id in types do for i, id in types do
if id == toAdd then if id == toAdd then
return -1 return -1
@ -326,7 +326,7 @@ function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
if #to.types > 0 then if #to.types > 0 then
-- When there is no previous archetype it should create the archetype -- When there is no previous archetype it should create the archetype
newEntity(entityId, record, to) newEntity(entityId, record, to)
onNotifyAdd(world, to, from, record.row, {componentId}) onNotifyAdd(world, to, from, record.row, { componentId })
end end
end end
@ -401,8 +401,8 @@ local function noop(_self: Query, ...: i53): () -> (number, ...any)
end end
local EmptyQuery = { local EmptyQuery = {
__iter = noop; __iter = noop,
without = noop; without = noop,
} }
EmptyQuery.__index = EmptyQuery EmptyQuery.__index = EmptyQuery
setmetatable(EmptyQuery, EmptyQuery) setmetatable(EmptyQuery, EmptyQuery)
@ -418,7 +418,7 @@ function World.query(world: World, ...: i53): Query
local compatibleArchetypes = {} local compatibleArchetypes = {}
local length = 0 local length = 0
local components = {...} local components = { ... }
local archetypes = world.archetypes local archetypes = world.archetypes
local queryLength = #components local queryLength = #components
@ -456,7 +456,7 @@ function World.query(world: World, ...: i53): Query
end end
length += 1 length += 1
compatibleArchetypes[length] = {archetype, indices} compatibleArchetypes[length] = { archetype, indices }
end end
local lastArchetype, compatibleArchetype = next(compatibleArchetypes) local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
@ -468,7 +468,7 @@ function World.query(world: World, ...: i53): Query
preparedQuery.__index = preparedQuery preparedQuery.__index = preparedQuery
function preparedQuery:without(...) function preparedQuery:without(...)
local withoutComponents = {...} local withoutComponents = { ... }
for i = #compatibleArchetypes, 1, -1 do for i = #compatibleArchetypes, 1, -1 do
local archetype = compatibleArchetypes[i][1] local archetype = compatibleArchetypes[i][1]
local records = archetype.records local records = archetype.records
@ -599,7 +599,7 @@ function World.delete(world: World, entityId: i53)
end end
function World.observer(world: World, ...) function World.observer(world: World, ...)
local componentIds = {...} local componentIds = { ... }
local idsCount = #componentIds local idsCount = #componentIds
local hooks = world.hooks local hooks = world.hooks
@ -647,13 +647,13 @@ function World.observer(world: World, ...)
return archetype.entities[row], unpack(queryOutput, 1, idsCount) return archetype.entities[row], unpack(queryOutput, 1, idsCount)
end end
end; end,
} }
end end
return table.freeze({ return table.freeze({
World = World; World = World,
ON_ADD = ON_ADD; ON_ADD = ON_ADD,
ON_REMOVE = ON_REMOVE; ON_REMOVE = ON_REMOVE,
ON_SET = ON_SET; ON_SET = ON_SET,
}) })

View file

@ -1,6 +1,6 @@
[tools] [tools]
wally = "upliftgames/wally@0.3.2" wally = "upliftgames/wally@0.3.2"
rojo = "rojo-rbx/rojo@7.4.1" rojo = "rojo-rbx/rojo@7.4.4"
stylua = "johnnymorganz/stylua@0.19.1" stylua = "johnnymorganz/stylua@0.20.0"
selene = "kampfkarren/selene@0.26.1" selene = "kampfkarren/selene@0.27.1"
Blink = "1Axen/Blink@0.14.1" Blink = "1Axen/Blink@0.14.1"

View file

@ -972,8 +972,6 @@ local function world_cleanup(world)
world.archetypeIndex = new_archetype_map world.archetypeIndex = new_archetype_map
end end
local world_delete: (world: World, entity: i53, destruct: boolean?) -> () local world_delete: (world: World, entity: i53, destruct: boolean?) -> ()
do do
function world_delete(world: World, entity: i53, destruct: boolean?) function world_delete(world: World, entity: i53, destruct: boolean?)

View file

@ -1,5 +1,10 @@
column_width = 120 column_width = 120
line_endings = "Unix"
indent_type = "Tabs"
indent_width = 4
quote_style = "ForceDouble" quote_style = "ForceDouble"
call_parentheses = "Always"
collapse_simple_statement = "Never"
[sort_requires] [sort_requires]
enabled = true enabled = true

View file

@ -1,152 +1,152 @@
-- original author @centauri -- original author @centauri
local bt local bt
do do
local FAILURE = 0
local SUCCESS = 1
local RUNNING = 2
local FAILURE = 0 local function SEQUENCE(nodes)
local SUCCESS = 1 return function(...)
local RUNNING = 2 for _, node in nodes do
local status = node(...)
local function SEQUENCE(nodes) if status == FAILURE or status == RUNNING then
return function(...) return status
for _, node in nodes do end
local status = node(...) end
if status == FAILURE or status == RUNNING then return SUCCESS
return status end
end end
end local function FALLBACK(nodes)
return SUCCESS return function(...)
end for _, node in nodes do
end local status = node(...)
local function FALLBACK(nodes) if status == SUCCESS or status == RUNNING then
return function(...) return status
for _, node in nodes do end
local status = node(...) end
if status == SUCCESS or status == RUNNING then return FAILURE
return status end
end end
end bt = {
return FAILURE SEQUENCE = SEQUENCE,
end FALLBACK = FALLBACK,
end RUNNING = RUNNING,
bt = { SUCCESS = SUCCESS,
SEQUENCE = SEQUENCE, FAILURE = FAILURE,
FALLBACK = FALLBACK, }
RUNNING = RUNNING,
SUCCESS = SUCCESS,
FAILURE = FAILURE,
}
end end
local SEQUENCE, FALLBACK = bt.SEQUENCE, bt.FALLBACK local SEQUENCE, FALLBACK = bt.SEQUENCE, bt.FALLBACK
local RUNNING, SUCCESS, FAILURE = bt.FAILURE, bt.SUCCESS, bt.FAILURE local RUNNING, SUCCESS, FAILURE = bt.FAILURE, bt.SUCCESS, bt.FAILURE
local btree = FALLBACK { local btree = FALLBACK({
SEQUENCE { SEQUENCE({
function() function()
return 1 return 1
end, end,
function() function()
return 0 return 0
end end,
}, }),
SEQUENCE { SEQUENCE({
function() function()
print(3) print(3)
local start = os.clock() local start = os.clock()
local now = os.clock() local now = os.clock()
while os.clock() - now < 4 do while os.clock() - now < 4 do
print("yielding") print("yielding")
coroutine.yield() coroutine.yield()
end end
return 0 return 0
end end,
}, }),
function() function()
return 1 return 1
end end,
} })
function wait(seconds) function wait(seconds)
local start = os.clock() local start = os.clock()
while os.clock() - start < seconds do end while os.clock() - start < seconds do
return os.clock() - start end
return os.clock() - start
end end
local function panic(str) local function panic(str)
-- We don't want to interrupt the loop when we error -- We don't want to interrupt the loop when we error
coroutine.resume(coroutine.create(function() error(str) end)) coroutine.resume(coroutine.create(function()
error(str)
end))
end end
local jecs = require("@jecs") local jecs = require("@jecs")
local world = jecs.World.new() local world = jecs.World.new()
local function Scheduler(world, ...) local function Scheduler(world, ...)
local systems = { ... } local systems = { ... }
local systemsNames = {} local systemsNames = {}
local N = #systems local N = #systems
local system local system
local dt local dt
for i, module in systems do for i, module in systems do
local sys = if typeof(module) == "function" then module else require(module) local sys = if typeof(module) == "function" then module else require(module)
systems[i] = sys systems[i] = sys
local file, line = debug.info(2, "sl") local file, line = debug.info(2, "sl")
systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}` systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
end end
local function run() local function run()
local name = systemsNames[system] local name = systemsNames[system]
--debug.profilebegin(name) --debug.profilebegin(name)
--debug.setmemorycategory(name) --debug.setmemorycategory(name)
system(world, dt) system(world, dt)
--debug.profileend() --debug.profileend()
end end
local function loop(sinceLastFrame) local function loop(sinceLastFrame)
--debug.profilebegin("loop()") --debug.profilebegin("loop()")
local start = os.clock() local start = os.clock()
for i = N, 1, -1 do for i = N, 1, -1 do
system = systems[i] system = systems[i]
dt = sinceLastFrame dt = sinceLastFrame
local didNotYield, why = xpcall(function() local didNotYield, why = xpcall(function()
for _ in run do end for _ in run do
end, debug.traceback) end
end, debug.traceback)
if didNotYield then if didNotYield then
continue continue
end end
if string.find(why, "thread is not yieldable") then if string.find(why, "thread is not yieldable") then
N -= 1 N -= 1
local name = table.remove(systems, i) local name = table.remove(systems, i)
panic("Not allowed to yield in the systems." panic("Not allowed to yield in the systems." .. "\n" .. `System: {name} has been ejected`)
.. "\n"
.. `System: {name} has been ejected`
)
else else
panic(why) panic(why)
end end
end end
--debug.profileend() --debug.profileend()
--debug.resetmemorycategory() --debug.resetmemorycategory()
return os.clock() - start return os.clock() - start
end end
return loop return loop
end end
local co = coroutine.create(btree) local co = coroutine.create(btree)
local function ai(world, dt) local function ai(world, dt)
coroutine.resume(co) coroutine.resume(co)
end end
local loop = Scheduler(world, ai) local loop = Scheduler(world, ai)
while wait(0.2) do while wait(0.2) do
print("frame time: ", loop(0.2)) print("frame time: ", loop(0.2))
end end

View file

@ -1,47 +1,47 @@
local jecs = require("@jecs") local jecs = require("@jecs")
local function create_cache(hook) local function create_cache(hook)
local columns = setmetatable({}, { local columns = setmetatable({}, {
__index = function(self, component) __index = function(self, component)
local column = {} local column = {}
self[component] = column self[component] = column
return column return column
end end,
}) })
return function(world, component, fn) return function(world, component, fn)
local column = columns[component] local column = columns[component]
table.insert(column, fn) table.insert(column, fn)
world:set(component, hook, function(entity, value) world:set(component, hook, function(entity, value)
for _, callback in column do for _, callback in column do
callback(entity, value) callback(entity, value)
end end
end) end)
end end
end end
local hooks = { local hooks = {
OnSet = create_cache(jecs.OnSet), OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd), OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove) OnRemove = create_cache(jecs.OnRemove),
} }
local world = jecs.World.new() local world = jecs.World.new()
local Position = world:component() local Position = world:component()
local order = "" local order = ""
hooks.OnSet(world, Position, function(entity, value) hooks.OnSet(world, Position, function(entity, value)
print("$1", entity, `({value.x}, {value.y}, {value.z})`) print("$1", entity, `({value.x}, {value.y}, {value.z})`)
order ..= "$1" order ..= "$1"
end) end)
hooks.OnSet(world, Position, function(entity, value) hooks.OnSet(world, Position, function(entity, value)
print("$2", entity, `\{{value.x}, {value.y}, {value.z}}`) print("$2", entity, `\{{value.x}, {value.y}, {value.z}}`)
order ..= "-$2" order ..= "-$2"
end) end)
world:set(world:entity(), Position, {x=1,y=0,z=1}) world:set(world:entity(), Position, { x = 1, y = 0, z = 1 })
-- Output: -- Output:
-- $1 270 (1, 0, 1) -- $1 270 (1, 0, 1)
-- $2 270 {1, 0, 1} -- $2 270 {1, 0, 1}
assert(order == "$1".."-".."$2") assert(order == "$1" .. "-" .. "$2")

View file

@ -1,56 +1,57 @@
local function calculateAverage(times) local function calculateAverage(times)
local sum = 0 local sum = 0
for _, time in ipairs(times) do for _, time in ipairs(times) do
sum = sum + time sum = sum + time
end end
return sum / #times return sum / #times
end end
-- Main logic to time the test function -- Main logic to time the test function
local CASES = { local CASES = {
jecs = function(world, ...) jecs = function(world, ...)
for i = 1, 100 do for i = 1, 100 do
local q = world:query(...) local q = world:query(...)
for _ in q do end for _ in q do
end end
end, end
mirror = function(world, ...) end,
for i = 1, 100 do mirror = function(world, ...)
local q = world:query(...) for i = 1, 100 do
for _ in q do end local q = world:query(...)
end for _ in q do
end end
end
end,
} }
for name, fn in CASES do for name, fn in CASES do
local times = {} local times = {}
local allocations = {} local allocations = {}
local ecs = require("@"..name) local ecs = require("@" .. name)
local world = ecs.World.new() local world = ecs.World.new()
local A, B, C = world:component(), world:component(), world:component() local A, B, C = world:component(), world:component(), world:component()
for i = 1, 5 do for i = 1, 5 do
local e = world:entity() local e = world:entity()
world:add(e, A) world:add(e, A)
world:add(e, B) world:add(e, B)
world:add(e, C) world:add(e, C)
end end
collectgarbage("collect") collectgarbage("collect")
local count = collectgarbage("count") local count = collectgarbage("count")
for i = 1, 50000 do for i = 1, 50000 do
local startTime = os.clock() local startTime = os.clock()
fn(world, A, B, C) fn(world, A, B, C)
local allocated = collectgarbage("count") local allocated = collectgarbage("count")
collectgarbage("collect") collectgarbage("collect")
local endTime = os.clock() local endTime = os.clock()
table.insert(times, endTime - startTime) table.insert(times, endTime - startTime)
table.insert(allocations, allocated) table.insert(allocations, allocated)
end end
print(name, "gc cycle time", calculateAverage(times)) print(name, "gc cycle time", calculateAverage(times))
print(name, "memory allocated", calculateAverage(allocations)) print(name, "memory allocated", calculateAverage(allocations))
end end

View file

@ -1,5 +1,5 @@
local testkit = require("@testkit")
local jecs = require("@jecs") local jecs = require("@jecs")
local testkit = require("@testkit")
local world = jecs.World.new() local world = jecs.World.new()

File diff suppressed because it is too large Load diff

View file

@ -41,7 +41,7 @@ local color = {
end, end,
orange = function(s: string): string orange = function(s: string): string
return if disable_ansi then s else `\27[38;5;208m{s}\27[0m` return if disable_ansi then s else `\27[38;5;208m{s}\27[0m`
end, end,
} }
@ -96,7 +96,7 @@ local function convert_units(unit: string, value: number): (number, string)
return value * sign, prefix_colors[order](prefixes[order] .. unit) return value * sign, prefix_colors[order](prefixes[order] .. unit)
end end
local WALL = color.gray "│" local WALL = color.gray("│")
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Testing -- Testing
@ -118,7 +118,7 @@ type Case = {
name: string, name: string,
result: number, result: number,
line: number?, line: number?,
focus: boolean focus: boolean,
} }
local PASS, FAIL, NONE, ERROR, SKIPPED = 1, 2, 3, 4, 5 local PASS, FAIL, NONE, ERROR, SKIPPED = 1, 2, 3, 4, 5
@ -129,48 +129,51 @@ local test: Test?
local tests: { Test } = {} local tests: { Test } = {}
local function output_test_result(test: Test) local function output_test_result(test: Test)
if check_for_focused then if check_for_focused then
local any_focused = test.focus local any_focused = test.focus
for _, case in test.cases do for _, case in test.cases do
any_focused = any_focused or case.focus any_focused = any_focused or case.focus
end end
if not any_focused then return end if not any_focused then
return
end
end end
print(color.white(test.name)) print(color.white(test.name))
for _, case in test.cases do for _, case in test.cases do
local status = ({ local status = ({
[PASS] = color.green "PASS", [PASS] = color.green("PASS"),
[FAIL] = color.red "FAIL", [FAIL] = color.red("FAIL"),
[NONE] = color.orange "NONE", [NONE] = color.orange("NONE"),
[ERROR] = color.red "FAIL", [ERROR] = color.red("FAIL"),
[SKIPPED] = color.yellow "SKIP" [SKIPPED] = color.yellow("SKIP"),
})[case.result] })[case.result]
local line = case.result == FAIL and color.red(`{case.line}:`) or "" local line = case.result == FAIL and color.red(`{case.line}:`) or ""
if check_for_focused and case.focus == false and test.focus == false then continue end if check_for_focused and case.focus == false and test.focus == false then
continue
end
print(`{status}{WALL} {line}{color.gray(case.name)}`) print(`{status}{WALL} {line}{color.gray(case.name)}`)
end end
if test.error then if test.error then
print(color.gray "error: " .. color.red(test.error.message)) print(color.gray("error: ") .. color.red(test.error.message))
print(color.gray "trace: " .. color.red(test.error.trace)) print(color.gray("trace: ") .. color.red(test.error.trace))
else else
print() print()
end end
end end
local function CASE(name: string) local function CASE(name: string)
skip = false skip = false
assert(test, "no active test") assert(test, "no active test")
local case = { local case = {
name = name, name = name,
result = NONE, result = NONE,
focus = false focus = false,
} }
test.case = case test.case = case
@ -183,7 +186,7 @@ local function CHECK<T>(value: T, stack: number?): T?
local case = test.case local case = test.case
if not case then if not case then
CASE "" CASE("")
case = test.case case = test.case
end end
@ -192,7 +195,7 @@ local function CHECK<T>(value: T, stack: number?): T?
if case.result ~= FAIL then if case.result ~= FAIL then
case.result = value and PASS or FAIL case.result = value and PASS or FAIL
if skip then if skip then
case.result = SKIPPED case.result = SKIPPED
end end
case.line = debug.info(stack and stack + 1 or 2, "l") case.line = debug.info(stack and stack + 1 or 2, "l")
end end
@ -208,7 +211,7 @@ local function TEST(name: string, fn: () -> ())
name = name, name = name,
cases = {}, cases = {},
duration = 0, duration = 0,
focus = false focus = false,
} }
assert(test) assert(test)
@ -221,7 +224,9 @@ local function TEST(name: string, fn: () -> ())
end) end)
test.duration = os.clock() - start test.duration = os.clock() - start
if not test.case then CASE "" end if not test.case then
CASE("")
end
assert(test.case, "no active case") assert(test.case, "no active case")
if not success then if not success then
@ -271,27 +276,14 @@ local function FINISH(): boolean
output_test_result(test) output_test_result(test)
end end
print( print(color.gray(string.format(`{passed_cases}/{total_cases} test cases passed in %.3f ms.`, duration * 1e3)))
color.gray(
string.format(
`{passed_cases}/{total_cases} test cases passed in %.3f ms.`,
duration * 1e3
)
)
)
if check_for_focused then if check_for_focused then
print( print(color.gray(`{passed_focus_cases}/{total_focus_cases} focused test cases passed`))
color.gray(`{passed_focus_cases}/{total_focus_cases} focused test cases passed`)
)
end end
local fails = total_cases - passed_cases local fails = total_cases - passed_cases
print( print((fails > 0 and color.red or color.green)(`{fails} {fails == 1 and "fail" or "fails"}`))
(fails > 0 and color.red or color.green)(
`{fails} {fails == 1 and "fail" or "fails"}`
)
)
check_for_focused = false check_for_focused = false
return success, table.clear(tests) return success, table.clear(tests)
@ -331,7 +323,7 @@ local function BENCH(name: string, fn: () -> ())
bench = {} bench = {}
assert(bench); assert(bench);
(collectgarbage :: any) "collect" (collectgarbage :: any)("collect")
local mem_start = gcinfo() local mem_start = gcinfo()
local time_start = os.clock() local time_start = os.clock()
@ -345,7 +337,7 @@ local function BENCH(name: string, fn: () -> ())
local mem_stop = gcinfo() local mem_stop = gcinfo()
if not success then if not success then
print(`{WALL}{color.red "ERROR"}{WALL} {name}`) print(`{WALL}{color.red("ERROR")}{WALL} {name}`)
print(color.gray(err_msg :: string)) print(color.gray(err_msg :: string))
else else
time_start = bench.time_start or time_start time_start = bench.time_start or time_start
@ -353,14 +345,10 @@ local function BENCH(name: string, fn: () -> ())
local n = bench.iterations or 1 local n = bench.iterations or 1
local d, d_unit = convert_units("s", (time_stop - time_start) / n) local d, d_unit = convert_units("s", (time_stop - time_start) / n)
local a, a_unit = local a, a_unit = convert_units("B", math.round((mem_stop - mem_start) / n * 1e3))
convert_units("B", math.round((mem_stop - mem_start) / n * 1e3))
local function round(x: number): string local function round(x: number): string
return x > 0 return x > 0 and x < 10 and (x - math.floor(x)) > 0 and string.format("%2.1f", x)
and x < 10
and (x - math.floor(x)) > 0
and string.format("%2.1f", x)
or string.format("%3.f", x) or string.format("%3.f", x)
end end
@ -394,9 +382,9 @@ local function print2(v: unknown)
if type(value) == "string" then if type(value) == "string" then
local n = str.n local n = str.n
str[n + 1] = '"' str[n + 1] = "\""
str[n + 2] = value str[n + 2] = value
str[n + 3] = '"' str[n + 3] = "\""
str.n = n + 3 str.n = n + 3
elseif type(value) ~= "table" then elseif type(value) ~= "table" then
local n = str.n local n = str.n
@ -468,25 +456,35 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local function shallow_eq(a: {}, b: {}): boolean local function shallow_eq(a: {}, b: {}): boolean
if #a ~= #b then return false end if #a ~= #b then
return false
end
for i, v in next, a do for i, v in next, a do
if b[i] ~= v then return false end if b[i] ~= v then
return false
end
end end
for i, v in next, b do for i, v in next, b do
if a[i] ~= v then return false end if a[i] ~= v then
return false
end
end end
return true return true
end end
local function deep_eq(a: {}, b: {}): boolean local function deep_eq(a: {}, b: {}): boolean
if #a ~= #b then return false end if #a ~= #b then
return false
end
for i, v in next, a do for i, v in next, a do
if type(b[i]) == "table" and type(v) == "table" then if type(b[i]) == "table" and type(v) == "table" then
if deep_eq(b[i], v) == false then return false end if deep_eq(b[i], v) == false then
return false
end
elseif b[i] ~= v then elseif b[i] ~= v then
return false return false
end end
@ -494,7 +492,9 @@ local function deep_eq(a: {}, b: {}): boolean
for i, v in next, b do for i, v in next, b do
if type(a[i]) == "table" and type(v) == "table" then if type(a[i]) == "table" and type(v) == "table" then
if deep_eq(a[i], v) == false then return false end if deep_eq(a[i], v) == false then
return false
end
elseif a[i] ~= v then elseif a[i] ~= v then
return false return false
end end