mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
parent
f82318c642
commit
e81b572480
45 changed files with 2867 additions and 2855 deletions
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
- name: Run Stylua
|
||||
uses: JohnnyMorganz/stylua-action@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: latest # NOTE: we recommend pinning to a specific version in case of formatting changes
|
||||
# CLI arguments
|
||||
args: ./src
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: latest # NOTE: we recommend pinning to a specific version in case of formatting changes
|
||||
# CLI arguments
|
||||
args: --check ./src
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
local jecs = require("@jecs")
|
||||
local mirror = require("../mirror/init")
|
||||
|
||||
|
@ -39,36 +38,36 @@ do
|
|||
|
||||
if flip() then
|
||||
combination ..= "B"
|
||||
ecs:set(entity, D2, {value = true})
|
||||
ecs:set(entity, D2, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "C"
|
||||
ecs:set(entity, D3, {value = true})
|
||||
ecs:set(entity, D3, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "D"
|
||||
ecs:set(entity, D4, {value = true})
|
||||
ecs:set(entity, D4, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "E"
|
||||
ecs:set(entity, D5, {value = true})
|
||||
ecs:set(entity, D5, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "F"
|
||||
ecs:set(entity, D6, {value = true})
|
||||
ecs:set(entity, D6, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "G"
|
||||
ecs:set(entity, D7, {value = true})
|
||||
ecs:set(entity, D7, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "H"
|
||||
ecs:set(entity, D8, {value = true})
|
||||
ecs:set(entity, D8, { value = true })
|
||||
end
|
||||
|
||||
if #combination == 7 then
|
||||
added += 1
|
||||
ecs:set(entity, D1, {value = true})
|
||||
ecs:set(entity, D1, { value = true })
|
||||
end
|
||||
archetypes[combination] = true
|
||||
end
|
||||
|
@ -117,36 +116,36 @@ do
|
|||
|
||||
if flip() then
|
||||
combination ..= "B"
|
||||
ecs:set(entity, D2, {value = true})
|
||||
ecs:set(entity, D2, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "C"
|
||||
ecs:set(entity, D3, {value = true})
|
||||
ecs:set(entity, D3, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "D"
|
||||
ecs:set(entity, D4, {value = true})
|
||||
ecs:set(entity, D4, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "E"
|
||||
ecs:set(entity, D5, {value = true})
|
||||
ecs:set(entity, D5, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "F"
|
||||
ecs:set(entity, D6, {value = true})
|
||||
ecs:set(entity, D6, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "G"
|
||||
ecs:set(entity, D7, {value = true})
|
||||
ecs:set(entity, D7, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "H"
|
||||
ecs:set(entity, D8, {value = true})
|
||||
ecs:set(entity, D8, { value = true })
|
||||
end
|
||||
|
||||
if #combination == 7 then
|
||||
added += 1
|
||||
ecs:set(entity, D1, {value = true})
|
||||
ecs:set(entity, D1, { value = true })
|
||||
end
|
||||
archetypes[combination] = true
|
||||
end
|
||||
|
|
|
@ -8,11 +8,12 @@ local function TITLE(s: string)
|
|||
print(testkit.color.white(s))
|
||||
end
|
||||
|
||||
local N = 2^17
|
||||
local N = 2 ^ 17
|
||||
|
||||
local pair = jecs.pair
|
||||
|
||||
do TITLE "create"
|
||||
do
|
||||
TITLE("create")
|
||||
local world = jecs.World.new()
|
||||
|
||||
BENCH("entity", function()
|
||||
|
@ -22,17 +23,17 @@ do TITLE "create"
|
|||
end)
|
||||
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local B = world:component()
|
||||
|
||||
BENCH("pair", function()
|
||||
for i = 1, START(N) do
|
||||
jecs.pair(A, B)
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
do TITLE "set"
|
||||
do
|
||||
TITLE("set")
|
||||
|
||||
local world = jecs.World.new()
|
||||
local A = world:entity()
|
||||
|
@ -65,7 +66,8 @@ end
|
|||
-- we have a separate benchmark for relationships.
|
||||
-- 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.
|
||||
do TITLE "set relationship"
|
||||
do
|
||||
TITLE("set relationship")
|
||||
|
||||
local world = jecs.World.new()
|
||||
local A = world:entity()
|
||||
|
@ -98,7 +100,8 @@ do TITLE "set relationship"
|
|||
end)
|
||||
end
|
||||
|
||||
do TITLE "get"
|
||||
do
|
||||
TITLE("get")
|
||||
|
||||
local world = jecs.World.new()
|
||||
local A = world:component()
|
||||
|
@ -140,7 +143,8 @@ do TITLE "get"
|
|||
end)
|
||||
end
|
||||
|
||||
do TITLE "target"
|
||||
do
|
||||
TITLE("target")
|
||||
|
||||
BENCH("1st target", function()
|
||||
local world = jecs.World.new()
|
||||
|
@ -164,14 +168,14 @@ do TITLE "target"
|
|||
world:target(entities[i], A, 0)
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
--- this benchmark is used to view how fragmentation affects query performance
|
||||
--- 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
|
||||
--- entities.
|
||||
do TITLE(`query {N} entities`)
|
||||
do
|
||||
TITLE(`query {N} entities`)
|
||||
|
||||
local function view_bench(n: number)
|
||||
BENCH(`{n} entities per archetype`, function()
|
||||
|
@ -194,14 +198,13 @@ do TITLE(`query {N} entities`)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
START()
|
||||
for id in world:query(A, B, C, D) do
|
||||
end
|
||||
end)
|
||||
|
||||
BENCH(`inlined query`, function()
|
||||
local world = jecs.World.new()
|
||||
local world = jecs.World.new()
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
@ -219,15 +222,14 @@ do TITLE(`query {N} entities`)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
START()
|
||||
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 b = columns[records[B].column]
|
||||
local c = columns[records[C].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]
|
||||
end
|
||||
end
|
||||
|
@ -235,7 +237,6 @@ do TITLE(`query {N} entities`)
|
|||
end
|
||||
|
||||
for i = 13, 0, -1 do
|
||||
view_bench(2^i)
|
||||
view_bench(2 ^ i)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ do
|
|||
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)
|
||||
BENCH("1 component", function()
|
||||
BENCH("1 component", function()
|
||||
for _ in world:query(A) do
|
||||
end
|
||||
end)
|
||||
|
@ -86,36 +86,36 @@ do
|
|||
|
||||
if flip() then
|
||||
combination ..= "B"
|
||||
ecs:set(entity, D2, {value = true})
|
||||
ecs:set(entity, D2, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "C"
|
||||
ecs:set(entity, D3, {value = true})
|
||||
ecs:set(entity, D3, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "D"
|
||||
ecs:set(entity, D4, {value = true})
|
||||
ecs:set(entity, D4, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "E"
|
||||
ecs:set(entity, D5, {value = true})
|
||||
ecs:set(entity, D5, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "F"
|
||||
ecs:set(entity, D6, {value = true})
|
||||
ecs:set(entity, D6, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "G"
|
||||
ecs:set(entity, D7, {value = true})
|
||||
ecs:set(entity, D7, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "H"
|
||||
ecs:set(entity, D8, {value = true})
|
||||
ecs:set(entity, D8, { value = true })
|
||||
end
|
||||
|
||||
if #combination == 7 then
|
||||
added += 1
|
||||
ecs:set(entity, D1, {value = true})
|
||||
ecs:set(entity, D1, { value = true })
|
||||
end
|
||||
archetypes[combination] = true
|
||||
end
|
||||
|
@ -202,36 +202,36 @@ do
|
|||
|
||||
if flip() then
|
||||
combination ..= "B"
|
||||
ecs:set(entity, D2, {value = true})
|
||||
ecs:set(entity, D2, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "C"
|
||||
ecs:set(entity, D3, {value = true})
|
||||
ecs:set(entity, D3, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "D"
|
||||
ecs:set(entity, D4, {value = true})
|
||||
ecs:set(entity, D4, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "E"
|
||||
ecs:set(entity, D5, {value = true})
|
||||
ecs:set(entity, D5, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "F"
|
||||
ecs:set(entity, D6, {value = true})
|
||||
ecs:set(entity, D6, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "G"
|
||||
ecs:set(entity, D7, {value = true})
|
||||
ecs:set(entity, D7, { value = true })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "H"
|
||||
ecs:set(entity, D8, {value = true})
|
||||
ecs:set(entity, D8, { value = true })
|
||||
end
|
||||
|
||||
if #combination == 7 then
|
||||
added += 1
|
||||
ecs:set(entity, D1, {value = true})
|
||||
ecs:set(entity, D1, { value = true })
|
||||
end
|
||||
archetypes[combination] = true
|
||||
end
|
||||
|
|
|
@ -12,37 +12,35 @@ local ecs = jecs.World.new()
|
|||
local A, B = Matter.component(), Matter.component()
|
||||
local C, D = ecs:component(), ecs:component()
|
||||
|
||||
|
||||
return {
|
||||
ParameterGenerator = function()
|
||||
local matter_entities = {}
|
||||
local jecs_entities = {}
|
||||
local entities = {
|
||||
matter = matter_entities,
|
||||
jecs = jecs_entities
|
||||
}
|
||||
for i = 1, 1000 do
|
||||
table.insert(matter_entities, newWorld:spawn(A(), B()))
|
||||
local e = ecs:entity()
|
||||
ecs:set(e, C, {})
|
||||
ecs:set(e, D, {})
|
||||
table.insert(jecs_entities, e)
|
||||
end
|
||||
return entities
|
||||
end;
|
||||
local matter_entities = {}
|
||||
local jecs_entities = {}
|
||||
local entities = {
|
||||
matter = matter_entities,
|
||||
jecs = jecs_entities,
|
||||
}
|
||||
for i = 1, 1000 do
|
||||
table.insert(matter_entities, newWorld:spawn(A(), B()))
|
||||
local e = ecs:entity()
|
||||
ecs:set(e, C, {})
|
||||
ecs:set(e, D, {})
|
||||
table.insert(jecs_entities, e)
|
||||
end
|
||||
return entities
|
||||
end,
|
||||
|
||||
Functions = {
|
||||
Matter = function(_, entities)
|
||||
for _, entity in entities.matter do
|
||||
newWorld:despawn(entity)
|
||||
end
|
||||
end;
|
||||
|
||||
for _, entity in entities.matter do
|
||||
newWorld:despawn(entity)
|
||||
end
|
||||
end,
|
||||
|
||||
Jecs = function(_, entities)
|
||||
for _, entity in entities.jecs do
|
||||
for _, entity in entities.jecs do
|
||||
ecs:delete(entity)
|
||||
end
|
||||
end;
|
||||
};
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
||||
local jecs = require(ReplicatedStorage.Lib)
|
||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
||||
local jecs = require(ReplicatedStorage.Lib)
|
||||
local newWorld = Matter.World.new()
|
||||
local ecs = jecs.World.new()
|
||||
local mirror = require(ReplicatedStorage.mirror)
|
||||
|
@ -45,7 +45,6 @@ local E6 = mcs:entity()
|
|||
local E7 = mcs:entity()
|
||||
local E8 = mcs:entity()
|
||||
|
||||
|
||||
local registry2 = ecr.registry()
|
||||
return {
|
||||
ParameterGenerator = function()
|
||||
|
@ -53,51 +52,49 @@ return {
|
|||
end,
|
||||
|
||||
Functions = {
|
||||
Matter = function()
|
||||
local e = newWorld:spawn()
|
||||
for i = 1, 5000 do
|
||||
newWorld:insert(e,
|
||||
A1({ value = true }),
|
||||
A2({ value = true }),
|
||||
A3({ value = true }),
|
||||
A4({ value = true }),
|
||||
A5({ value = true }),
|
||||
A6({ value = true }),
|
||||
A7({ value = true }),
|
||||
A8({ value = true })
|
||||
)
|
||||
end
|
||||
Matter = function()
|
||||
local e = newWorld:spawn()
|
||||
for i = 1, 5000 do
|
||||
newWorld:insert(
|
||||
e,
|
||||
A1({ value = true }),
|
||||
A2({ value = true }),
|
||||
A3({ value = true }),
|
||||
A4({ value = true }),
|
||||
A5({ value = true }),
|
||||
A6({ value = true }),
|
||||
A7({ value = true }),
|
||||
A8({ value = true })
|
||||
)
|
||||
end
|
||||
end,
|
||||
|
||||
|
||||
ECR = function()
|
||||
local e = registry2.create()
|
||||
for i = 1, 5000 do
|
||||
registry2:set(e, B1, {value = false})
|
||||
registry2:set(e, B2, {value = false})
|
||||
registry2:set(e, B3, {value = false})
|
||||
registry2:set(e, B4, {value = false})
|
||||
registry2:set(e, B5, {value = false})
|
||||
registry2:set(e, B6, {value = false})
|
||||
registry2:set(e, B7, {value = false})
|
||||
registry2:set(e, B8, {value = false})
|
||||
end
|
||||
ECR = function()
|
||||
local e = registry2.create()
|
||||
for i = 1, 5000 do
|
||||
registry2:set(e, B1, { value = false })
|
||||
registry2:set(e, B2, { value = false })
|
||||
registry2:set(e, B3, { value = false })
|
||||
registry2:set(e, B4, { value = false })
|
||||
registry2:set(e, B5, { value = false })
|
||||
registry2:set(e, B6, { value = false })
|
||||
registry2:set(e, B7, { value = false })
|
||||
registry2:set(e, B8, { value = false })
|
||||
end
|
||||
end,
|
||||
|
||||
|
||||
Jecs = function()
|
||||
local e = ecs:entity()
|
||||
for i = 1, 5000 do
|
||||
ecs:set(e, C1, {value = false})
|
||||
ecs:set(e, C2, {value = false})
|
||||
ecs:set(e, C3, {value = false})
|
||||
ecs:set(e, C4, {value = false})
|
||||
ecs:set(e, C5, {value = false})
|
||||
ecs:set(e, C6, {value = false})
|
||||
ecs:set(e, C7, {value = false})
|
||||
ecs:set(e, C8, {value = false})
|
||||
|
||||
end
|
||||
Jecs = function()
|
||||
local e = ecs:entity()
|
||||
for i = 1, 5000 do
|
||||
ecs:set(e, C1, { value = false })
|
||||
ecs:set(e, C2, { value = false })
|
||||
ecs:set(e, C3, { value = false })
|
||||
ecs:set(e, C4, { value = false })
|
||||
ecs:set(e, C5, { value = false })
|
||||
ecs:set(e, C6, { value = false })
|
||||
ecs:set(e, C7, { value = false })
|
||||
ecs:set(e, C8, { value = false })
|
||||
end
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
--!native
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local rgb = require(ReplicatedStorage.rgb)
|
||||
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 rgb = require(ReplicatedStorage.rgb)
|
||||
local newWorld = Matter.World.new()
|
||||
|
||||
local jecs = require(ReplicatedStorage.Lib)
|
||||
|
@ -39,7 +39,6 @@ local D6 = ecs:component()
|
|||
local D7 = ecs:component()
|
||||
local D8 = ecs:component()
|
||||
|
||||
|
||||
local E1 = mcs:entity()
|
||||
local E2 = mcs:entity()
|
||||
local E3 = mcs:entity()
|
||||
|
@ -49,14 +48,13 @@ local E6 = mcs:entity()
|
|||
local E7 = mcs:entity()
|
||||
local E8 = mcs:entity()
|
||||
|
||||
|
||||
local registry2 = ecr.registry()
|
||||
|
||||
local function flip()
|
||||
return math.random() >= 0.25
|
||||
end
|
||||
|
||||
local N = 2^16-2
|
||||
local N = 2 ^ 16 - 2
|
||||
local archetypes = {}
|
||||
|
||||
local hm = 0
|
||||
|
@ -68,69 +66,66 @@ for i = 1, N do
|
|||
local m = mcs:entity()
|
||||
|
||||
if flip() then
|
||||
registry2:set(id, B1, {value = true})
|
||||
ecs:set(entity, D1, { value = true})
|
||||
newWorld:insert(n, A1({value = true}))
|
||||
mcs:set(m, E1, { value = 2})
|
||||
registry2:set(id, B1, { value = true })
|
||||
ecs:set(entity, D1, { value = true })
|
||||
newWorld:insert(n, A1({ value = true }))
|
||||
mcs:set(m, E1, { value = 2 })
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "B"
|
||||
registry2:set(id, B2, {value = true})
|
||||
ecs:set(entity, D2, { value = true})
|
||||
mcs:set(m, E2, { value = 2})
|
||||
newWorld:insert(n, A2({value = true}))
|
||||
registry2:set(id, B2, { value = true })
|
||||
ecs:set(entity, D2, { value = true })
|
||||
mcs:set(m, E2, { value = 2 })
|
||||
newWorld:insert(n, A2({ value = true }))
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "C"
|
||||
registry2:set(id, B3, {value = true})
|
||||
ecs:set(entity, D3, { value = true})
|
||||
mcs:set(m, E3, { value = 2})
|
||||
newWorld:insert(n, A3({value = true}))
|
||||
registry2:set(id, B3, { value = true })
|
||||
ecs:set(entity, D3, { value = true })
|
||||
mcs:set(m, E3, { value = 2 })
|
||||
newWorld:insert(n, A3({ value = true }))
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "D"
|
||||
registry2:set(id, B4, {value = true})
|
||||
ecs:set(entity, D4, { value = true})
|
||||
mcs:set(m, E4, { value = 2})
|
||||
registry2:set(id, B4, { value = true })
|
||||
ecs:set(entity, D4, { value = true })
|
||||
mcs:set(m, E4, { value = 2 })
|
||||
|
||||
newWorld:insert(n, A4({value = true}))
|
||||
newWorld:insert(n, A4({ value = true }))
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "E"
|
||||
registry2:set(id, B5, {value = true})
|
||||
ecs:set(entity, D5, { value = true})
|
||||
mcs:set(m, E5, { value = 2})
|
||||
registry2:set(id, B5, { value = true })
|
||||
ecs:set(entity, D5, { value = true })
|
||||
mcs:set(m, E5, { value = 2 })
|
||||
|
||||
newWorld:insert(n, A5({value = true}))
|
||||
newWorld:insert(n, A5({ value = true }))
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "F"
|
||||
registry2:set(id, B6, {value = true})
|
||||
ecs:set(entity, D6, { value = true})
|
||||
mcs:set(m, E6, { value = 2})
|
||||
newWorld:insert(n, A6({value = true}))
|
||||
registry2:set(id, B6, { value = true })
|
||||
ecs:set(entity, D6, { value = true })
|
||||
mcs:set(m, E6, { value = 2 })
|
||||
newWorld:insert(n, A6({ value = true }))
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "G"
|
||||
registry2:set(id, B7, {value = true})
|
||||
ecs:set(entity, D7, { value = true})
|
||||
mcs:set(m, E7, { value = 2})
|
||||
newWorld:insert(n, A7({value = true}))
|
||||
registry2:set(id, B7, { value = true })
|
||||
ecs:set(entity, D7, { value = true })
|
||||
mcs:set(m, E7, { value = 2 })
|
||||
newWorld:insert(n, A7({ value = true }))
|
||||
end
|
||||
if flip() then
|
||||
combination ..= "H"
|
||||
registry2:set(id, B8, {value = true})
|
||||
newWorld:insert(n, A8({value = true}))
|
||||
ecs:set(entity, D8, { value = true})
|
||||
mcs:set(m, E8, { value = 2})
|
||||
|
||||
registry2:set(id, B8, { value = true })
|
||||
newWorld:insert(n, A8({ value = true }))
|
||||
ecs:set(entity, D8, { value = true })
|
||||
mcs:set(m, E8, { value = 2 })
|
||||
end
|
||||
|
||||
|
||||
|
||||
if combination:find("BCDF") then
|
||||
if not archetypes[combination] then
|
||||
print(combination)
|
||||
print(combination)
|
||||
end
|
||||
hm += 1
|
||||
end
|
||||
|
@ -148,7 +143,7 @@ local WALL = gray(" │ ")
|
|||
local count = 0
|
||||
|
||||
for _, archetype in ecs:query(D2, D4, D6, D8):archetypes() do
|
||||
count += #archetype.entities
|
||||
count += #archetype.entities
|
||||
end
|
||||
|
||||
print(count)
|
||||
|
@ -165,13 +160,13 @@ return {
|
|||
end,
|
||||
|
||||
ECR = function()
|
||||
for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do
|
||||
end
|
||||
end,
|
||||
for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do
|
||||
end
|
||||
end,
|
||||
|
||||
Jecs = function()
|
||||
for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do
|
||||
end
|
||||
for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do
|
||||
end
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -14,25 +14,25 @@ return {
|
|||
local registry2 = ecr.registry()
|
||||
|
||||
return registry2
|
||||
end;
|
||||
end,
|
||||
|
||||
Functions = {
|
||||
Matter = function()
|
||||
for i = 1, 1000 do
|
||||
newWorld:spawn()
|
||||
end
|
||||
end;
|
||||
end,
|
||||
|
||||
ECR = function(_, registry2)
|
||||
for i = 1, 1000 do
|
||||
registry2.create()
|
||||
end
|
||||
end;
|
||||
end,
|
||||
|
||||
Jecs = function()
|
||||
for i = 1, 1000 do
|
||||
ecs:entity()
|
||||
end
|
||||
end;
|
||||
};
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -10,11 +10,12 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|||
local RunService = game:GetService("RunService")
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
@ -31,128 +32,128 @@ local RecieveInstances = {}
|
|||
local RecieveInstanceCursor = 0
|
||||
|
||||
type Entry = {
|
||||
value: any,
|
||||
next: Entry?
|
||||
value: any,
|
||||
next: Entry?,
|
||||
}
|
||||
|
||||
type Queue = {
|
||||
head: Entry?,
|
||||
tail: Entry?
|
||||
head: Entry?,
|
||||
tail: Entry?,
|
||||
}
|
||||
|
||||
type BufferSave = {
|
||||
Size: number,
|
||||
Cursor: number,
|
||||
Buffer: buffer,
|
||||
Instances: {Instance}
|
||||
Size: number,
|
||||
Cursor: number,
|
||||
Buffer: buffer,
|
||||
Instances: { Instance },
|
||||
}
|
||||
|
||||
local function Read(Bytes: number)
|
||||
local Offset = RecieveCursor
|
||||
RecieveCursor += Bytes
|
||||
return Offset
|
||||
local Offset = RecieveCursor
|
||||
RecieveCursor += Bytes
|
||||
return Offset
|
||||
end
|
||||
|
||||
local function Save(): BufferSave
|
||||
return {
|
||||
Size = SendSize,
|
||||
Cursor = SendCursor,
|
||||
Buffer = SendBuffer,
|
||||
Instances = SendInstances
|
||||
}
|
||||
return {
|
||||
Size = SendSize,
|
||||
Cursor = SendCursor,
|
||||
Buffer = SendBuffer,
|
||||
Instances = SendInstances,
|
||||
}
|
||||
end
|
||||
|
||||
local function Load(Save: BufferSave?)
|
||||
if Save then
|
||||
SendSize = Save.Size
|
||||
SendCursor = Save.Cursor
|
||||
SendOffset = Save.Cursor
|
||||
SendBuffer = Save.Buffer
|
||||
SendInstances = Save.Instances
|
||||
return
|
||||
end
|
||||
if Save then
|
||||
SendSize = Save.Size
|
||||
SendCursor = Save.Cursor
|
||||
SendOffset = Save.Cursor
|
||||
SendBuffer = Save.Buffer
|
||||
SendInstances = Save.Instances
|
||||
return
|
||||
end
|
||||
|
||||
SendSize = 64
|
||||
SendCursor = 0
|
||||
SendOffset = 0
|
||||
SendBuffer = buffer.create(64)
|
||||
SendInstances = {}
|
||||
SendSize = 64
|
||||
SendCursor = 0
|
||||
SendOffset = 0
|
||||
SendBuffer = buffer.create(64)
|
||||
SendInstances = {}
|
||||
end
|
||||
|
||||
local function Invoke()
|
||||
if Invocations == 255 then
|
||||
Invocations = 0
|
||||
end
|
||||
if Invocations == 255 then
|
||||
Invocations = 0
|
||||
end
|
||||
|
||||
local Invocation = Invocations
|
||||
Invocations += 1
|
||||
return Invocation
|
||||
local Invocation = Invocations
|
||||
Invocations += 1
|
||||
return Invocation
|
||||
end
|
||||
|
||||
local function Allocate(Bytes: number)
|
||||
local InUse = (SendCursor + Bytes)
|
||||
if InUse > SendSize then
|
||||
--> Avoid resizing the buffer for every write
|
||||
while InUse > SendSize do
|
||||
SendSize *= 1.5
|
||||
end
|
||||
local InUse = (SendCursor + Bytes)
|
||||
if InUse > SendSize then
|
||||
--> Avoid resizing the buffer for every write
|
||||
while InUse > SendSize do
|
||||
SendSize *= 1.5
|
||||
end
|
||||
|
||||
local Buffer = buffer.create(SendSize)
|
||||
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
|
||||
SendBuffer = Buffer
|
||||
end
|
||||
local Buffer = buffer.create(SendSize)
|
||||
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
|
||||
SendBuffer = Buffer
|
||||
end
|
||||
|
||||
SendOffset = SendCursor
|
||||
SendCursor += Bytes
|
||||
|
||||
return SendOffset
|
||||
SendOffset = SendCursor
|
||||
SendCursor += Bytes
|
||||
|
||||
return SendOffset
|
||||
end
|
||||
|
||||
local function CreateQueue(): Queue
|
||||
return {
|
||||
head = nil,
|
||||
tail = nil
|
||||
}
|
||||
return {
|
||||
head = nil,
|
||||
tail = nil,
|
||||
}
|
||||
end
|
||||
|
||||
local function Pop(queue: Queue): any
|
||||
local head = queue.head
|
||||
if head == nil then
|
||||
return
|
||||
end
|
||||
local head = queue.head
|
||||
if head == nil then
|
||||
return
|
||||
end
|
||||
|
||||
queue.head = head.next
|
||||
return head.value
|
||||
queue.head = head.next
|
||||
return head.value
|
||||
end
|
||||
|
||||
local function Push(queue: Queue, value: any)
|
||||
local entry: Entry = {
|
||||
value = value,
|
||||
next = nil
|
||||
}
|
||||
local entry: Entry = {
|
||||
value = value,
|
||||
next = nil,
|
||||
}
|
||||
|
||||
if queue.tail ~= nil then
|
||||
queue.tail.next = entry
|
||||
end
|
||||
if queue.tail ~= nil then
|
||||
queue.tail.next = entry
|
||||
end
|
||||
|
||||
queue.tail = entry
|
||||
queue.tail = entry
|
||||
|
||||
if queue.head == nil then
|
||||
queue.head = entry
|
||||
end
|
||||
if queue.head == nil then
|
||||
queue.head = entry
|
||||
end
|
||||
end
|
||||
|
||||
local Types = {}
|
||||
local Calls = table.create(256)
|
||||
|
||||
local Events: any = {
|
||||
Reliable = table.create(256),
|
||||
Unreliable = table.create(256)
|
||||
Reliable = table.create(256),
|
||||
Unreliable = table.create(256),
|
||||
}
|
||||
|
||||
local Queue: any = {
|
||||
Reliable = table.create(256),
|
||||
Unreliable = table.create(256)
|
||||
Reliable = table.create(256),
|
||||
Unreliable = table.create(256),
|
||||
}
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
|
||||
local function StepReplication()
|
||||
if SendCursor <= 0 then
|
||||
return
|
||||
end
|
||||
if SendCursor <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local Buffer = buffer.create(SendCursor)
|
||||
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
|
||||
Reliable:FireServer(Buffer, SendInstances)
|
||||
local Buffer = buffer.create(SendCursor)
|
||||
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
|
||||
Reliable:FireServer(Buffer, SendInstances)
|
||||
|
||||
SendSize = 64
|
||||
SendCursor = 0
|
||||
SendOffset = 0
|
||||
SendBuffer = buffer.create(64)
|
||||
table.clear(SendInstances)
|
||||
SendSize = 64
|
||||
SendCursor = 0
|
||||
SendOffset = 0
|
||||
SendBuffer = buffer.create(64)
|
||||
table.clear(SendInstances)
|
||||
end
|
||||
|
||||
local Elapsed = 0
|
||||
|
@ -246,13 +246,13 @@ RunService.Heartbeat:Connect(function(DeltaTime: number)
|
|||
end
|
||||
end)
|
||||
|
||||
Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
|
||||
Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: { Instance })
|
||||
RecieveCursor = 0
|
||||
RecieveBuffer = Buffer
|
||||
RecieveInstances = Instances
|
||||
RecieveInstanceCursor = 0
|
||||
local Size = buffer.len(RecieveBuffer)
|
||||
while (RecieveCursor < Size) do
|
||||
while RecieveCursor < Size do
|
||||
-- Read BLOCK: 1 bytes
|
||||
local BLOCK_START = Read(1)
|
||||
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
|
||||
|
@ -262,13 +262,13 @@ Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
|
|||
end
|
||||
end)
|
||||
|
||||
Unreliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
|
||||
Unreliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: { Instance })
|
||||
RecieveCursor = 0
|
||||
RecieveBuffer = Buffer
|
||||
RecieveInstances = Instances
|
||||
RecieveInstanceCursor = 0
|
||||
local Size = buffer.len(RecieveBuffer)
|
||||
while (RecieveCursor < Size) do
|
||||
while RecieveCursor < Size do
|
||||
-- Read BLOCK: 1 bytes
|
||||
local BLOCK_START = Read(1)
|
||||
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
|
||||
|
@ -285,53 +285,52 @@ return {
|
|||
Iter = function(): () -> (number, number, CFrame)
|
||||
local index = 0
|
||||
local queue = Queue.Unreliable[0]
|
||||
return function (): (number, number, CFrame)
|
||||
return function(): (number, number, CFrame)
|
||||
index += 1
|
||||
local arguments = Pop(queue)
|
||||
if arguments ~= nil then
|
||||
return index, unpack(arguments, 1, arguments.n)
|
||||
end
|
||||
return
|
||||
return
|
||||
end
|
||||
end,
|
||||
Next = function(): () -> (number, number, CFrame)
|
||||
local index = 0
|
||||
local queue = Queue.Unreliable[0]
|
||||
return function (): (number, number, CFrame)
|
||||
return function(): (number, number, CFrame)
|
||||
index += 1
|
||||
local arguments = Pop(queue)
|
||||
if arguments ~= nil then
|
||||
return index, unpack(arguments, 1, arguments.n)
|
||||
end
|
||||
return
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
SpawnMob = {
|
||||
Iter = function(): () -> (number, number, CFrame, number)
|
||||
local index = 0
|
||||
local queue = Queue.Reliable[0]
|
||||
return function (): (number, number, CFrame, number)
|
||||
return function(): (number, number, CFrame, number)
|
||||
index += 1
|
||||
local arguments = Pop(queue)
|
||||
if arguments ~= nil then
|
||||
return index, unpack(arguments, 1, arguments.n)
|
||||
end
|
||||
return
|
||||
return
|
||||
end
|
||||
end,
|
||||
Next = function(): () -> (number, number, CFrame, number)
|
||||
local index = 0
|
||||
local queue = Queue.Reliable[0]
|
||||
return function (): (number, number, CFrame, number)
|
||||
return function(): (number, number, CFrame, number)
|
||||
index += 1
|
||||
local arguments = Pop(queue)
|
||||
if arguments ~= nil then
|
||||
return index, unpack(arguments, 1, arguments.n)
|
||||
end
|
||||
return
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,23 +11,24 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|||
local RunService = game:GetService("RunService")
|
||||
|
||||
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
|
||||
|
||||
local Reliable: RemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent
|
||||
if not Reliable then
|
||||
local RemoteEvent = Instance.new("RemoteEvent")
|
||||
RemoteEvent.Name = "BLINK_RELIABLE_REMOTE"
|
||||
RemoteEvent.Parent = ReplicatedStorage
|
||||
Reliable = RemoteEvent
|
||||
local RemoteEvent = Instance.new("RemoteEvent")
|
||||
RemoteEvent.Name = "BLINK_RELIABLE_REMOTE"
|
||||
RemoteEvent.Parent = ReplicatedStorage
|
||||
Reliable = RemoteEvent
|
||||
end
|
||||
|
||||
local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
|
||||
local Unreliable: UnreliableRemoteEvent =
|
||||
ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
|
||||
if not Unreliable then
|
||||
local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")
|
||||
UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE"
|
||||
UnreliableRemoteEvent.Parent = ReplicatedStorage
|
||||
Unreliable = UnreliableRemoteEvent
|
||||
local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")
|
||||
UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE"
|
||||
UnreliableRemoteEvent.Parent = ReplicatedStorage
|
||||
Unreliable = UnreliableRemoteEvent
|
||||
end
|
||||
|
||||
local Invocations = 0
|
||||
|
@ -45,131 +46,130 @@ local RecieveInstances = {}
|
|||
local RecieveInstanceCursor = 0
|
||||
|
||||
type Entry = {
|
||||
value: any,
|
||||
next: Entry?
|
||||
value: any,
|
||||
next: Entry?,
|
||||
}
|
||||
|
||||
type Queue = {
|
||||
head: Entry?,
|
||||
tail: Entry?
|
||||
head: Entry?,
|
||||
tail: Entry?,
|
||||
}
|
||||
|
||||
type BufferSave = {
|
||||
Size: number,
|
||||
Cursor: number,
|
||||
Buffer: buffer,
|
||||
Instances: {Instance}
|
||||
Size: number,
|
||||
Cursor: number,
|
||||
Buffer: buffer,
|
||||
Instances: { Instance },
|
||||
}
|
||||
|
||||
local function Read(Bytes: number)
|
||||
local Offset = RecieveCursor
|
||||
RecieveCursor += Bytes
|
||||
return Offset
|
||||
local Offset = RecieveCursor
|
||||
RecieveCursor += Bytes
|
||||
return Offset
|
||||
end
|
||||
|
||||
local function Save(): BufferSave
|
||||
return {
|
||||
Size = SendSize,
|
||||
Cursor = SendCursor,
|
||||
Buffer = SendBuffer,
|
||||
Instances = SendInstances
|
||||
}
|
||||
return {
|
||||
Size = SendSize,
|
||||
Cursor = SendCursor,
|
||||
Buffer = SendBuffer,
|
||||
Instances = SendInstances,
|
||||
}
|
||||
end
|
||||
|
||||
local function Load(Save: BufferSave?)
|
||||
if Save then
|
||||
SendSize = Save.Size
|
||||
SendCursor = Save.Cursor
|
||||
SendOffset = Save.Cursor
|
||||
SendBuffer = Save.Buffer
|
||||
SendInstances = Save.Instances
|
||||
return
|
||||
end
|
||||
if Save then
|
||||
SendSize = Save.Size
|
||||
SendCursor = Save.Cursor
|
||||
SendOffset = Save.Cursor
|
||||
SendBuffer = Save.Buffer
|
||||
SendInstances = Save.Instances
|
||||
return
|
||||
end
|
||||
|
||||
SendSize = 64
|
||||
SendCursor = 0
|
||||
SendOffset = 0
|
||||
SendBuffer = buffer.create(64)
|
||||
SendInstances = {}
|
||||
SendSize = 64
|
||||
SendCursor = 0
|
||||
SendOffset = 0
|
||||
SendBuffer = buffer.create(64)
|
||||
SendInstances = {}
|
||||
end
|
||||
|
||||
local function Invoke()
|
||||
if Invocations == 255 then
|
||||
Invocations = 0
|
||||
end
|
||||
if Invocations == 255 then
|
||||
Invocations = 0
|
||||
end
|
||||
|
||||
local Invocation = Invocations
|
||||
Invocations += 1
|
||||
return Invocation
|
||||
local Invocation = Invocations
|
||||
Invocations += 1
|
||||
return Invocation
|
||||
end
|
||||
|
||||
local function Allocate(Bytes: number)
|
||||
local InUse = (SendCursor + Bytes)
|
||||
if InUse > SendSize then
|
||||
--> Avoid resizing the buffer for every write
|
||||
while InUse > SendSize do
|
||||
SendSize *= 1.5
|
||||
end
|
||||
local InUse = (SendCursor + Bytes)
|
||||
if InUse > SendSize then
|
||||
--> Avoid resizing the buffer for every write
|
||||
while InUse > SendSize do
|
||||
SendSize *= 1.5
|
||||
end
|
||||
|
||||
local Buffer = buffer.create(SendSize)
|
||||
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
|
||||
SendBuffer = Buffer
|
||||
end
|
||||
local Buffer = buffer.create(SendSize)
|
||||
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
|
||||
SendBuffer = Buffer
|
||||
end
|
||||
|
||||
SendOffset = SendCursor
|
||||
SendCursor += Bytes
|
||||
|
||||
return SendOffset
|
||||
SendOffset = SendCursor
|
||||
SendCursor += Bytes
|
||||
|
||||
return SendOffset
|
||||
end
|
||||
|
||||
local function CreateQueue(): Queue
|
||||
return {
|
||||
head = nil,
|
||||
tail = nil
|
||||
}
|
||||
return {
|
||||
head = nil,
|
||||
tail = nil,
|
||||
}
|
||||
end
|
||||
|
||||
local function Pop(queue: Queue): any
|
||||
local head = queue.head
|
||||
if head == nil then
|
||||
return
|
||||
end
|
||||
local head = queue.head
|
||||
if head == nil then
|
||||
return
|
||||
end
|
||||
|
||||
queue.head = head.next
|
||||
return head.value
|
||||
queue.head = head.next
|
||||
return head.value
|
||||
end
|
||||
|
||||
local function Push(queue: Queue, value: any)
|
||||
local entry: Entry = {
|
||||
value = value,
|
||||
next = nil
|
||||
}
|
||||
local entry: Entry = {
|
||||
value = value,
|
||||
next = nil,
|
||||
}
|
||||
|
||||
if queue.tail ~= nil then
|
||||
queue.tail.next = entry
|
||||
end
|
||||
if queue.tail ~= nil then
|
||||
queue.tail.next = entry
|
||||
end
|
||||
|
||||
queue.tail = entry
|
||||
queue.tail = entry
|
||||
|
||||
if queue.head == nil then
|
||||
queue.head = entry
|
||||
end
|
||||
if queue.head == nil then
|
||||
queue.head = entry
|
||||
end
|
||||
end
|
||||
|
||||
local Types = {}
|
||||
local Calls = table.create(256)
|
||||
|
||||
local Events: any = {
|
||||
Reliable = table.create(256),
|
||||
Unreliable = table.create(256)
|
||||
Reliable = table.create(256),
|
||||
Unreliable = table.create(256),
|
||||
}
|
||||
|
||||
local Queue: any = {
|
||||
Reliable = table.create(256),
|
||||
Unreliable = table.create(256)
|
||||
Reliable = table.create(256),
|
||||
Unreliable = table.create(256),
|
||||
}
|
||||
|
||||
|
||||
function Types.ReadEVENT_UpdateTransform(): (number, CFrame)
|
||||
-- Read BLOCK: 32 bytes
|
||||
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)
|
||||
end
|
||||
|
||||
|
||||
local PlayersMap: {[Player]: BufferSave} = {}
|
||||
local PlayersMap: { [Player]: BufferSave } = {}
|
||||
|
||||
Players.PlayerRemoving:Connect(function(Player)
|
||||
PlayersMap[Player] = nil
|
||||
PlayersMap[Player] = nil
|
||||
end)
|
||||
|
||||
local function StepReplication()
|
||||
for Player, Send in PlayersMap do
|
||||
if Send.Cursor <= 0 then
|
||||
continue
|
||||
end
|
||||
for Player, Send in PlayersMap do
|
||||
if Send.Cursor <= 0 then
|
||||
continue
|
||||
end
|
||||
|
||||
local Buffer = buffer.create(Send.Cursor)
|
||||
buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor)
|
||||
Reliable:FireClient(Player, Buffer, Send.Instances)
|
||||
local Buffer = buffer.create(Send.Cursor)
|
||||
buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor)
|
||||
Reliable:FireClient(Player, Buffer, Send.Instances)
|
||||
|
||||
Send.Size = 64
|
||||
Send.Cursor = 0
|
||||
Send.Buffer = buffer.create(64)
|
||||
table.clear(Send.Instances)
|
||||
end
|
||||
Send.Size = 64
|
||||
Send.Cursor = 0
|
||||
Send.Buffer = buffer.create(64)
|
||||
table.clear(Send.Instances)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
RecieveBuffer = Buffer
|
||||
RecieveInstances = Instances
|
||||
RecieveInstanceCursor = 0
|
||||
local Size = buffer.len(RecieveBuffer)
|
||||
while (RecieveCursor < Size) do
|
||||
while RecieveCursor < Size do
|
||||
-- Read BLOCK: 1 bytes
|
||||
local BLOCK_START = Read(1)
|
||||
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
|
||||
end
|
||||
end)
|
||||
|
||||
Unreliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: {Instance})
|
||||
Unreliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: { Instance })
|
||||
RecieveCursor = 0
|
||||
RecieveBuffer = Buffer
|
||||
RecieveInstances = Instances
|
||||
RecieveInstanceCursor = 0
|
||||
local Size = buffer.len(RecieveBuffer)
|
||||
while (RecieveCursor < Size) do
|
||||
while RecieveCursor < Size do
|
||||
-- Read BLOCK: 1 bytes
|
||||
local BLOCK_START = Read(1)
|
||||
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
|
||||
|
@ -302,7 +301,7 @@ return {
|
|||
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
|
||||
Unreliable:FireAllClients(Buffer, SendInstances)
|
||||
end,
|
||||
FireList = function(List: {Player}, Value1: number, Value2: CFrame): ()
|
||||
FireList = function(List: { Player }, Value1: number, Value2: CFrame): ()
|
||||
Load()
|
||||
Types.WriteEVENT_UpdateTransform(Value1, Value2)
|
||||
local Buffer = buffer.create(SendCursor)
|
||||
|
@ -342,7 +341,7 @@ return {
|
|||
PlayersMap[Player] = Save()
|
||||
end
|
||||
end,
|
||||
FireList = function(List: {Player}, Value1: number, Value2: CFrame, Value3: number): ()
|
||||
FireList = function(List: { Player }, Value1: number, Value2: CFrame, Value3: number): ()
|
||||
Load()
|
||||
Types.WriteEVENT_SpawnMob(Value1, Value2, Value3)
|
||||
local Buffer, Size, Instances = SendBuffer, SendCursor, SendInstances
|
||||
|
@ -370,5 +369,4 @@ return {
|
|||
end
|
||||
end,
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
_G.JECS_DEBUG = true
|
||||
_G.JECS_HI_COMPONENT_ID = 32
|
||||
require(game:GetService("ReplicatedStorage").ecs)
|
||||
return
|
||||
_G.JECS_DEBUG = true
|
||||
_G.JECS_HI_COMPONENT_ID = 32
|
||||
require(game:GetService("ReplicatedStorage").ecs)
|
||||
return
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jabby = require(ReplicatedStorage.Packages.jabby)
|
||||
local std = require(ReplicatedStorage.std)
|
||||
local Scheduler = std.Scheduler
|
||||
local world = std.world
|
||||
|
||||
local function start(modules)
|
||||
local scheduler = Scheduler.new(world, require(ReplicatedStorage.std.components))
|
||||
for _, module in modules do
|
||||
require(module)(scheduler)
|
||||
end
|
||||
local events = scheduler.collect.all()
|
||||
scheduler.systems.begin(events)
|
||||
local scheduler = Scheduler.new(world, require(ReplicatedStorage.std.components))
|
||||
for _, module in modules do
|
||||
require(module)(scheduler)
|
||||
end
|
||||
local events = scheduler.collect.all()
|
||||
scheduler.systems.begin(events)
|
||||
|
||||
jabby.set_check_function(function(player)
|
||||
return true
|
||||
end)
|
||||
if RunService:IsClient() then
|
||||
local client = jabby.obtain_client()
|
||||
local dtor
|
||||
UserInputService.InputBegan:Connect(function(input)
|
||||
if input.KeyCode == Enum.KeyCode.F4 then
|
||||
if dtor then
|
||||
dtor()
|
||||
end
|
||||
dtor = client.spawn_app(client.apps.home)
|
||||
end
|
||||
end)
|
||||
jabby.set_check_function(function(player)
|
||||
return true
|
||||
end)
|
||||
if RunService:IsClient() then
|
||||
local client = jabby.obtain_client()
|
||||
local dtor
|
||||
UserInputService.InputBegan:Connect(function(input)
|
||||
if input.KeyCode == Enum.KeyCode.F4 then
|
||||
if dtor then
|
||||
dtor()
|
||||
end
|
||||
dtor = client.spawn_app(client.apps.home)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,33 +8,33 @@ local FAILURE = 1
|
|||
local RUNNING = 2
|
||||
|
||||
local function SEQUENCE(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == FAILURE or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return SUCCESS
|
||||
end
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == FAILURE or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return SUCCESS
|
||||
end
|
||||
end
|
||||
|
||||
local function FALLBACK(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == SUCCESS or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return FAILURE
|
||||
end
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == SUCCESS or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return FAILURE
|
||||
end
|
||||
end
|
||||
|
||||
local bt = {
|
||||
SEQUENCE = SEQUENCE,
|
||||
FALLBACK = FALLBACK,
|
||||
RUNNING = RUNNING
|
||||
SEQUENCE = SEQUENCE,
|
||||
FALLBACK = FALLBACK,
|
||||
RUNNING = RUNNING,
|
||||
}
|
||||
|
||||
return bt
|
||||
|
|
|
@ -1,133 +1,139 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
type World = jecs.World
|
||||
|
||||
type Tracker<T> = { track: (world: World, fn: (changes: {
|
||||
added: () -> () -> (number, T),
|
||||
removed: () -> () -> number,
|
||||
changed: () -> () -> (number, T, T)
|
||||
}) -> ()) -> ()
|
||||
type Tracker<T> = {
|
||||
track: (
|
||||
world: World,
|
||||
fn: (
|
||||
changes: {
|
||||
added: () -> () -> (number, T),
|
||||
removed: () -> () -> number,
|
||||
changed: () -> () -> (number, T, T),
|
||||
}
|
||||
) -> ()
|
||||
) -> (),
|
||||
}
|
||||
|
||||
type Entity<T = any> = number & { __nominal_type_dont_use: T }
|
||||
|
||||
local function diff(a, b)
|
||||
local size = 0
|
||||
for k, v in a do
|
||||
if b[k] ~= v then
|
||||
return true
|
||||
end
|
||||
size += 1
|
||||
end
|
||||
for k, v in b do
|
||||
size -= 1
|
||||
end
|
||||
local size = 0
|
||||
for k, v in a do
|
||||
if b[k] ~= v then
|
||||
return true
|
||||
end
|
||||
size += 1
|
||||
end
|
||||
for k, v in b do
|
||||
size -= 1
|
||||
end
|
||||
|
||||
if size ~= 0 then
|
||||
return true
|
||||
end
|
||||
if size ~= 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
return false
|
||||
end
|
||||
|
||||
local function ChangeTracker<T>(world: World, T: Entity<T>): Tracker<T>
|
||||
local sparse = world.entityIndex.sparse
|
||||
local PreviousT = jecs.pair(jecs.Rest, T)
|
||||
local add = {}
|
||||
local added
|
||||
local removed
|
||||
local is_trivial
|
||||
local sparse = world.entityIndex.sparse
|
||||
local PreviousT = jecs.pair(jecs.Rest, T)
|
||||
local add = {}
|
||||
local added
|
||||
local removed
|
||||
local is_trivial
|
||||
|
||||
local function changes_added()
|
||||
added = true
|
||||
local q = world:query(T):without(PreviousT):drain()
|
||||
return function()
|
||||
local id, data = q.next()
|
||||
if not id then
|
||||
return nil
|
||||
end
|
||||
local function changes_added()
|
||||
added = true
|
||||
local q = world:query(T):without(PreviousT):drain()
|
||||
return function()
|
||||
local id, data = q.next()
|
||||
if not id then
|
||||
return nil
|
||||
end
|
||||
|
||||
is_trivial = typeof(data) ~= "table"
|
||||
is_trivial = typeof(data) ~= "table"
|
||||
|
||||
add[id] = data
|
||||
add[id] = data
|
||||
|
||||
return id, data
|
||||
end
|
||||
end
|
||||
return id, data
|
||||
end
|
||||
end
|
||||
|
||||
local function changes_changed()
|
||||
local q = world:query(T, PreviousT):drain()
|
||||
local function changes_changed()
|
||||
local q = world:query(T, PreviousT):drain()
|
||||
|
||||
return function()
|
||||
local id, new, old = q.next()
|
||||
while true do
|
||||
if not id then
|
||||
return nil
|
||||
end
|
||||
return function()
|
||||
local id, new, old = q.next()
|
||||
while true do
|
||||
if not id then
|
||||
return nil
|
||||
end
|
||||
|
||||
if not is_trivial then
|
||||
if diff(new, old) then
|
||||
break
|
||||
end
|
||||
elseif new ~= old then
|
||||
break
|
||||
end
|
||||
if not is_trivial then
|
||||
if diff(new, old) then
|
||||
break
|
||||
end
|
||||
elseif new ~= old then
|
||||
break
|
||||
end
|
||||
|
||||
id, new, old = q.next()
|
||||
end
|
||||
id, new, old = q.next()
|
||||
end
|
||||
|
||||
local record = sparse[id]
|
||||
local archetype = record.archetype
|
||||
local column = archetype.records[PreviousT].column
|
||||
local data = if is_trivial then new else table.clone(new)
|
||||
archetype.columns[column][record.row] = data
|
||||
local record = sparse[id]
|
||||
local archetype = record.archetype
|
||||
local column = archetype.records[PreviousT].column
|
||||
local data = if is_trivial then new else table.clone(new)
|
||||
archetype.columns[column][record.row] = data
|
||||
|
||||
return id, old, new
|
||||
end
|
||||
end
|
||||
return id, old, new
|
||||
end
|
||||
end
|
||||
|
||||
local function changes_removed()
|
||||
removed = true
|
||||
local function changes_removed()
|
||||
removed = true
|
||||
|
||||
local q = world:query(PreviousT):without(T):drain()
|
||||
return function()
|
||||
local id = q.next()
|
||||
if id then
|
||||
world:remove(id, PreviousT)
|
||||
end
|
||||
return id
|
||||
end
|
||||
end
|
||||
local q = world:query(PreviousT):without(T):drain()
|
||||
return function()
|
||||
local id = q.next()
|
||||
if id then
|
||||
world:remove(id, PreviousT)
|
||||
end
|
||||
return id
|
||||
end
|
||||
end
|
||||
|
||||
local changes = {
|
||||
added = changes_added,
|
||||
changed = changes_changed,
|
||||
removed = changes_removed,
|
||||
}
|
||||
local changes = {
|
||||
added = changes_added,
|
||||
changed = changes_changed,
|
||||
removed = changes_removed,
|
||||
}
|
||||
|
||||
local function track(fn)
|
||||
added = false
|
||||
removed = false
|
||||
local function track(fn)
|
||||
added = false
|
||||
removed = false
|
||||
|
||||
fn(changes)
|
||||
fn(changes)
|
||||
|
||||
if not added then
|
||||
for _ in changes_added() do
|
||||
end
|
||||
end
|
||||
if not added then
|
||||
for _ in changes_added() do
|
||||
end
|
||||
end
|
||||
|
||||
if not removed then
|
||||
for _ in changes_removed() do
|
||||
end
|
||||
end
|
||||
if not removed then
|
||||
for _ in changes_removed() do
|
||||
end
|
||||
end
|
||||
|
||||
for e, data in add do
|
||||
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
|
||||
end
|
||||
end
|
||||
for e, data in add do
|
||||
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
|
||||
end
|
||||
end
|
||||
|
||||
local tracker = { track = track }
|
||||
local tracker = { track = track }
|
||||
|
||||
return tracker
|
||||
return tracker
|
||||
end
|
||||
|
||||
return ChangeTracker
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
local world = require(script.Parent.world)
|
||||
|
||||
type Entity<T=nil> = jecs.Entity<T>
|
||||
type Entity<T = nil> = jecs.Entity<T>
|
||||
local components: {
|
||||
Character: Entity<Model>,
|
||||
Mob: Entity,
|
||||
Model: Entity<Model>,
|
||||
Player: Entity,
|
||||
Target: Entity,
|
||||
Transform: Entity<CFrame>,
|
||||
Velocity: Entity<number>,
|
||||
Previous: Entity,
|
||||
} = {
|
||||
Character = world:component(),
|
||||
Mob = world:component(),
|
||||
Model = world:component(),
|
||||
Player = world:component(),
|
||||
Target = world:component(),
|
||||
Transform = world:component(),
|
||||
Velocity = world:component(),
|
||||
Previous = world:component(),
|
||||
}
|
||||
Character: Entity<Model>,
|
||||
Mob: Entity,
|
||||
Model: Entity<Model>,
|
||||
Player: Entity,
|
||||
Target: Entity,
|
||||
Transform: Entity<CFrame>,
|
||||
Velocity: Entity<number>,
|
||||
Previous: Entity,
|
||||
} =
|
||||
{
|
||||
Character = world:component(),
|
||||
Mob = world:component(),
|
||||
Model = world:component(),
|
||||
Player = world:component(),
|
||||
Target = world:component(),
|
||||
Transform = world:component(),
|
||||
Velocity = world:component(),
|
||||
Previous = world:component(),
|
||||
}
|
||||
|
||||
return table.freeze(components)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
local world = require(script.Parent.world)
|
||||
local handle = require(script.Parent.handle)
|
||||
local world = require(script.Parent.world)
|
||||
|
||||
local singleton = world:entity()
|
||||
|
||||
local function ctx()
|
||||
-- Cannot cache handles because they will get invalidated
|
||||
return handle(singleton)
|
||||
-- Cannot cache handles because they will get invalidated
|
||||
return handle(singleton)
|
||||
end
|
||||
|
||||
return ctx
|
||||
|
|
|
@ -2,55 +2,55 @@ local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
|||
local world = require(script.Parent.world)
|
||||
|
||||
type Handle = {
|
||||
has: (self: Handle, id: jecs.Entity) -> boolean,
|
||||
get: <T>(self: Handle, id: jecs.Entity<T>) -> T?,
|
||||
add: <T>(self: Handle, id: jecs.Entity<T>) -> Handle,
|
||||
set: <T>(self: Handle, id: jecs.Entity<T>, value: T) -> Handle,
|
||||
id: (self: Handle?) -> jecs.Entity
|
||||
has: (self: Handle, id: jecs.Entity) -> boolean,
|
||||
get: <T>(self: Handle, id: jecs.Entity<T>) -> T?,
|
||||
add: <T>(self: Handle, id: jecs.Entity<T>) -> Handle,
|
||||
set: <T>(self: Handle, id: jecs.Entity<T>, value: T) -> Handle,
|
||||
id: (self: Handle?) -> jecs.Entity,
|
||||
}
|
||||
|
||||
local handle: (e: jecs.Entity) -> Handle
|
||||
|
||||
do
|
||||
local e
|
||||
local function has(_, id)
|
||||
return world:has(e, id)
|
||||
end
|
||||
local function get(_, id)
|
||||
return world:get(e, id)
|
||||
end
|
||||
local function set(self, id, value)
|
||||
world:set(e, id, value)
|
||||
return self
|
||||
end
|
||||
local function add(self, id)
|
||||
world:add(e, id)
|
||||
return self
|
||||
end
|
||||
local function clear(self)
|
||||
world:clear(e)
|
||||
return self
|
||||
end
|
||||
local function delete(self)
|
||||
world:delete(e)
|
||||
end
|
||||
local function id()
|
||||
return e
|
||||
end
|
||||
local e
|
||||
local function has(_, id)
|
||||
return world:has(e, id)
|
||||
end
|
||||
local function get(_, id)
|
||||
return world:get(e, id)
|
||||
end
|
||||
local function set(self, id, value)
|
||||
world:set(e, id, value)
|
||||
return self
|
||||
end
|
||||
local function add(self, id)
|
||||
world:add(e, id)
|
||||
return self
|
||||
end
|
||||
local function clear(self)
|
||||
world:clear(e)
|
||||
return self
|
||||
end
|
||||
local function delete(self)
|
||||
world:delete(e)
|
||||
end
|
||||
local function id()
|
||||
return e
|
||||
end
|
||||
|
||||
local entity = {
|
||||
has = has,
|
||||
get = get,
|
||||
set = set,
|
||||
add = add,
|
||||
clear = clear,
|
||||
id = id,
|
||||
}
|
||||
local entity = {
|
||||
has = has,
|
||||
get = get,
|
||||
set = set,
|
||||
add = add,
|
||||
clear = clear,
|
||||
id = id,
|
||||
}
|
||||
|
||||
function handle(id)
|
||||
e = id
|
||||
return entity
|
||||
end
|
||||
function handle(id)
|
||||
e = id
|
||||
return entity
|
||||
end
|
||||
end
|
||||
|
||||
return handle
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
--!native
|
||||
--!optimize 2
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
|
||||
local function create_cache(hook)
|
||||
local columns = setmetatable({}, {
|
||||
__index = function(self, component)
|
||||
local column = {}
|
||||
self[component] = column
|
||||
return column
|
||||
end
|
||||
})
|
||||
|
||||
return function(world, component, fn)
|
||||
local column = columns[component]
|
||||
table.insert(column, fn)
|
||||
world:set(component, hook, function(entity, value)
|
||||
for _, callback in column do
|
||||
callback(entity, value)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local hooks = {
|
||||
OnSet = create_cache(jecs.OnSet),
|
||||
OnAdd = create_cache(jecs.OnAdd),
|
||||
OnRemove = create_cache(jecs.OnRemove)
|
||||
}
|
||||
|
||||
return hooks
|
||||
--!native
|
||||
--!optimize 2
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
|
||||
local function create_cache(hook)
|
||||
local columns = setmetatable({}, {
|
||||
__index = function(self, component)
|
||||
local column = {}
|
||||
self[component] = column
|
||||
return column
|
||||
end,
|
||||
})
|
||||
|
||||
return function(world, component, fn)
|
||||
local column = columns[component]
|
||||
table.insert(column, fn)
|
||||
world:set(component, hook, function(entity, value)
|
||||
for _, callback in column do
|
||||
callback(entity, value)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local hooks = {
|
||||
OnSet = create_cache(jecs.OnSet),
|
||||
OnAdd = create_cache(jecs.OnAdd),
|
||||
OnRemove = create_cache(jecs.OnRemove),
|
||||
}
|
||||
|
||||
return hooks
|
||||
|
|
|
@ -7,19 +7,19 @@ local Scheduler = require(script.scheduler)
|
|||
export type Scheduler = Scheduler.Scheduler
|
||||
|
||||
local std = {
|
||||
ChangeTracker = require(script.changetracker),
|
||||
Scheduler = Scheduler,
|
||||
bt = require(script.bt),
|
||||
collect = require(script.collect),
|
||||
components = require(script.components),
|
||||
ctx = require(script.ctx),
|
||||
handle = require(script.handle),
|
||||
interval = require(script.interval),
|
||||
ref = require(script.ref),
|
||||
world = world :: World,
|
||||
pair = jecs.pair,
|
||||
__ = jecs.w,
|
||||
hooks = require(script.hooks)
|
||||
ChangeTracker = require(script.changetracker),
|
||||
Scheduler = Scheduler,
|
||||
bt = require(script.bt),
|
||||
collect = require(script.collect),
|
||||
components = require(script.components),
|
||||
ctx = require(script.ctx),
|
||||
handle = require(script.handle),
|
||||
interval = require(script.interval),
|
||||
ref = require(script.ref),
|
||||
world = world :: World,
|
||||
pair = jecs.pair,
|
||||
__ = jecs.w,
|
||||
hooks = require(script.hooks),
|
||||
}
|
||||
|
||||
return std
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
local function interval(s)
|
||||
local pin
|
||||
local pin
|
||||
|
||||
local function throttle()
|
||||
if not pin then
|
||||
pin = os.clock()
|
||||
end
|
||||
local function throttle()
|
||||
if not pin then
|
||||
pin = os.clock()
|
||||
end
|
||||
|
||||
local elapsed = os.clock() - pin > s
|
||||
if elapsed then
|
||||
pin = os.clock()
|
||||
end
|
||||
local elapsed = os.clock() - pin > s
|
||||
if elapsed then
|
||||
pin = os.clock()
|
||||
end
|
||||
|
||||
return elapsed
|
||||
end
|
||||
return throttle
|
||||
return elapsed
|
||||
end
|
||||
return throttle
|
||||
end
|
||||
|
||||
return interval
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
local world = require(script.Parent.world)
|
||||
local handle = require(script.Parent.handle)
|
||||
local world = require(script.Parent.world)
|
||||
local refs = {}
|
||||
|
||||
local function fini(key)
|
||||
return function()
|
||||
refs[key] = nil
|
||||
end
|
||||
return function()
|
||||
refs[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function ref(key): (handle.Handle, (() -> ())?)
|
||||
if not key then
|
||||
return handle(world:entity())
|
||||
end
|
||||
local e = refs[key]
|
||||
if not e then
|
||||
e = world:entity()
|
||||
refs[key] = e
|
||||
end
|
||||
-- Cannot cache handles because they will get invalidated
|
||||
return handle(e), fini(key)
|
||||
if not key then
|
||||
return handle(world:entity())
|
||||
end
|
||||
local e = refs[key]
|
||||
if not e then
|
||||
e = world:entity()
|
||||
refs[key] = e
|
||||
end
|
||||
-- Cannot cache handles because they will get invalidated
|
||||
return handle(e), fini(key)
|
||||
end
|
||||
|
||||
return ref
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
local reserved = 0
|
||||
|
||||
local function reserve()
|
||||
reserved += 1
|
||||
return reserved
|
||||
reserved += 1
|
||||
return reserved
|
||||
end
|
||||
|
||||
-- If you don't like passing around a world singleton
|
||||
|
@ -19,13 +19,13 @@ end
|
|||
world:set(e, components.Transform, CFrame)
|
||||
]]
|
||||
local function register(world)
|
||||
for _ = 1, reserved do
|
||||
world:component()
|
||||
end
|
||||
return world
|
||||
for _ = 1, reserved do
|
||||
world:component()
|
||||
end
|
||||
return world
|
||||
end
|
||||
|
||||
return {
|
||||
reserve = reserve,
|
||||
register = register,
|
||||
reserve = reserve,
|
||||
register = register,
|
||||
}
|
||||
|
|
|
@ -7,245 +7,250 @@ local pair = jecs.pair
|
|||
local Name = jecs.Name
|
||||
|
||||
type World = jecs.World
|
||||
type Entity<T=nil> = jecs.Entity<T>
|
||||
type Entity<T = nil> = jecs.Entity<T>
|
||||
|
||||
type System = {
|
||||
callback: (world: World) -> (),
|
||||
id: number,
|
||||
callback: (world: World) -> (),
|
||||
id: number,
|
||||
}
|
||||
|
||||
type Systems = { System }
|
||||
|
||||
|
||||
type Events = {
|
||||
RenderStepped: Systems,
|
||||
Heartbeat: Systems
|
||||
RenderStepped: Systems,
|
||||
Heartbeat: Systems,
|
||||
}
|
||||
|
||||
export type Scheduler = {
|
||||
components: {
|
||||
Disabled: Entity,
|
||||
System: Entity<System>,
|
||||
Phase: Entity,
|
||||
DependsOn: Entity
|
||||
},
|
||||
components: {
|
||||
Disabled: Entity,
|
||||
System: Entity<System>,
|
||||
Phase: Entity,
|
||||
DependsOn: Entity,
|
||||
},
|
||||
|
||||
collect: {
|
||||
under_event: (event: Entity) -> Systems,
|
||||
all: () -> Events
|
||||
},
|
||||
collect: {
|
||||
under_event: (event: Entity) -> Systems,
|
||||
all: () -> Events,
|
||||
},
|
||||
|
||||
systems: {
|
||||
begin: (events: Events) -> { [Entity]: thread },
|
||||
new: (callback: (dt: number) -> (), phase: Entity) -> Entity
|
||||
},
|
||||
systems: {
|
||||
begin: (events: Events) -> { [Entity]: thread },
|
||||
new: (callback: (dt: number) -> (), phase: Entity) -> Entity,
|
||||
},
|
||||
|
||||
phases: {
|
||||
RenderStepped: Entity,
|
||||
Heartbeat: Entity,
|
||||
},
|
||||
phases: {
|
||||
RenderStepped: Entity,
|
||||
Heartbeat: Entity,
|
||||
},
|
||||
|
||||
phase: (after: Entity) -> Entity,
|
||||
phase: (after: Entity) -> Entity,
|
||||
|
||||
debugging: boolean,
|
||||
debugging: boolean,
|
||||
}
|
||||
|
||||
local scheduler_new: (w: World, components: { [string]: Entity }) -> Scheduler
|
||||
|
||||
do
|
||||
local world: World
|
||||
local Disabled: Entity
|
||||
local System: Entity<System>
|
||||
local DependsOn: Entity
|
||||
local Phase: Entity
|
||||
local Event: Entity<RBXScriptSignal>
|
||||
local world: World
|
||||
local Disabled: Entity
|
||||
local System: Entity<System>
|
||||
local DependsOn: Entity
|
||||
local Phase: Entity
|
||||
local Event: Entity<RBXScriptSignal>
|
||||
|
||||
local scheduler
|
||||
local scheduler
|
||||
|
||||
local RenderStepped
|
||||
local Heartbeat
|
||||
local PreAnimation
|
||||
local PreSimulation
|
||||
local RenderStepped
|
||||
local Heartbeat
|
||||
local PreAnimation
|
||||
local PreSimulation
|
||||
|
||||
local sys: System
|
||||
local dt
|
||||
local sys: System
|
||||
local dt
|
||||
|
||||
local function run()
|
||||
local id = sys.id
|
||||
local system_data = scheduler.system_data[id]
|
||||
if system_data.paused then return end
|
||||
local function run()
|
||||
local id = sys.id
|
||||
local system_data = scheduler.system_data[id]
|
||||
if system_data.paused then
|
||||
return
|
||||
end
|
||||
|
||||
scheduler:mark_system_frame_start(id)
|
||||
sys.callback(dt)
|
||||
scheduler:mark_system_frame_end(id)
|
||||
end
|
||||
scheduler:mark_system_frame_start(id)
|
||||
sys.callback(dt)
|
||||
scheduler:mark_system_frame_end(id)
|
||||
end
|
||||
|
||||
local function panic(str)
|
||||
-- We don't want to interrupt the loop when we error
|
||||
task.spawn(error, str)
|
||||
end
|
||||
local function begin(events: { Systems })
|
||||
local threads = {}
|
||||
for event, systems in events do
|
||||
if not event then continue end
|
||||
local event_name = tostring(event)
|
||||
threads[event] = task.spawn(function()
|
||||
while true do
|
||||
dt = event:Wait()
|
||||
local function panic(str)
|
||||
-- We don't want to interrupt the loop when we error
|
||||
task.spawn(error, str)
|
||||
end
|
||||
local function begin(events: { Systems })
|
||||
local threads = {}
|
||||
for event, systems in events do
|
||||
if not event then
|
||||
continue
|
||||
end
|
||||
local event_name = tostring(event)
|
||||
threads[event] = task.spawn(function()
|
||||
while true do
|
||||
dt = event:Wait()
|
||||
|
||||
debug.profilebegin(event_name)
|
||||
for _, s in systems do
|
||||
sys = s
|
||||
local didNotYield, why = xpcall(function()
|
||||
for _ in run do break end
|
||||
end, debug.traceback)
|
||||
debug.profilebegin(event_name)
|
||||
for _, s in systems do
|
||||
sys = s
|
||||
local didNotYield, why = xpcall(function()
|
||||
for _ in run do
|
||||
break
|
||||
end
|
||||
end, debug.traceback)
|
||||
|
||||
if didNotYield then
|
||||
continue
|
||||
end
|
||||
if didNotYield then
|
||||
continue
|
||||
end
|
||||
|
||||
if string.find(why, "thread is not yieldable") then
|
||||
panic("Not allowed to yield in the systems."
|
||||
.. "\n"
|
||||
.. "System: "
|
||||
.. debug.info(s.callback, "n")
|
||||
.. " has been ejected"
|
||||
)
|
||||
continue
|
||||
end
|
||||
panic(why)
|
||||
end
|
||||
debug.profileend()
|
||||
end
|
||||
end)
|
||||
end
|
||||
return threads
|
||||
end
|
||||
if string.find(why, "thread is not yieldable") then
|
||||
panic(
|
||||
"Not allowed to yield in the systems."
|
||||
.. "\n"
|
||||
.. "System: "
|
||||
.. debug.info(s.callback, "n")
|
||||
.. " has been ejected"
|
||||
)
|
||||
continue
|
||||
end
|
||||
panic(why)
|
||||
end
|
||||
debug.profileend()
|
||||
end
|
||||
end)
|
||||
end
|
||||
return threads
|
||||
end
|
||||
|
||||
local function scheduler_collect_systems_under_phase_recursive(systems, phase)
|
||||
local phase_name = world:get(phase, Name)
|
||||
for _, s in world:query(System):with(pair(DependsOn, phase)) do
|
||||
table.insert(systems, {
|
||||
id = scheduler:register_system({
|
||||
name = s.name,
|
||||
phase = phase_name
|
||||
}),
|
||||
callback = s.callback
|
||||
})
|
||||
end
|
||||
for after in world:query(Phase):with(pair(DependsOn, phase)) do
|
||||
scheduler_collect_systems_under_phase_recursive(systems, after)
|
||||
end
|
||||
end
|
||||
local function scheduler_collect_systems_under_phase_recursive(systems, phase)
|
||||
local phase_name = world:get(phase, Name)
|
||||
for _, s in world:query(System):with(pair(DependsOn, phase)) do
|
||||
table.insert(systems, {
|
||||
id = scheduler:register_system({
|
||||
name = s.name,
|
||||
phase = phase_name,
|
||||
}),
|
||||
callback = s.callback,
|
||||
})
|
||||
end
|
||||
for after in world:query(Phase):with(pair(DependsOn, phase)) do
|
||||
scheduler_collect_systems_under_phase_recursive(systems, after)
|
||||
end
|
||||
end
|
||||
|
||||
local function scheduler_collect_systems_under_event(event)
|
||||
local systems = {}
|
||||
scheduler_collect_systems_under_phase_recursive(systems, event)
|
||||
return systems
|
||||
end
|
||||
local function scheduler_collect_systems_under_event(event)
|
||||
local systems = {}
|
||||
scheduler_collect_systems_under_phase_recursive(systems, event)
|
||||
return systems
|
||||
end
|
||||
|
||||
local function scheduler_collect_systems_all()
|
||||
local events = {}
|
||||
for phase, event in world:query(Event):with(Phase) do
|
||||
events[event] = scheduler_collect_systems_under_event(phase)
|
||||
end
|
||||
return events
|
||||
end
|
||||
local function scheduler_collect_systems_all()
|
||||
local events = {}
|
||||
for phase, event in world:query(Event):with(Phase) do
|
||||
events[event] = scheduler_collect_systems_under_event(phase)
|
||||
end
|
||||
return events
|
||||
end
|
||||
|
||||
local function scheduler_phase_new(after)
|
||||
local phase = world:entity()
|
||||
world:add(phase, Phase)
|
||||
local dependency = pair(DependsOn, after)
|
||||
world:add(phase, dependency)
|
||||
return phase
|
||||
end
|
||||
local function scheduler_phase_new(after)
|
||||
local phase = world:entity()
|
||||
world:add(phase, Phase)
|
||||
local dependency = pair(DependsOn, after)
|
||||
world:add(phase, dependency)
|
||||
return phase
|
||||
end
|
||||
|
||||
local function scheduler_systems_new(callback, phase)
|
||||
local system = world:entity()
|
||||
local name = debug.info(callback, "n")
|
||||
world:set(system, System, { callback = callback, name = name })
|
||||
world:add(system, pair(DependsOn, phase))
|
||||
return system
|
||||
end
|
||||
local function scheduler_systems_new(callback, phase)
|
||||
local system = world:entity()
|
||||
local name = debug.info(callback, "n")
|
||||
world:set(system, System, { callback = callback, name = name })
|
||||
world:add(system, pair(DependsOn, phase))
|
||||
return system
|
||||
end
|
||||
|
||||
function scheduler_new(w: World, components: { [string]: Entity })
|
||||
world = w
|
||||
Disabled = world:component()
|
||||
System = world:component()
|
||||
Phase = world:component()
|
||||
DependsOn = world:component()
|
||||
Event = world:component()
|
||||
function scheduler_new(w: World, components: { [string]: Entity })
|
||||
world = w
|
||||
Disabled = world:component()
|
||||
System = world:component()
|
||||
Phase = world:component()
|
||||
DependsOn = world:component()
|
||||
Event = world:component()
|
||||
|
||||
RenderStepped = world:component()
|
||||
Heartbeat = world:component()
|
||||
PreSimulation = world:component()
|
||||
PreAnimation = world:component()
|
||||
RenderStepped = world:component()
|
||||
Heartbeat = world:component()
|
||||
PreSimulation = world:component()
|
||||
PreAnimation = world:component()
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
if RunService:IsClient() then
|
||||
world:add(RenderStepped, Phase)
|
||||
world:set(RenderStepped, Event, RunService.RenderStepped)
|
||||
end
|
||||
local RunService = game:GetService("RunService")
|
||||
if RunService:IsClient() then
|
||||
world:add(RenderStepped, Phase)
|
||||
world:set(RenderStepped, Event, RunService.RenderStepped)
|
||||
end
|
||||
|
||||
world:add(Heartbeat, Phase)
|
||||
world:set(Heartbeat, Event, RunService.Heartbeat)
|
||||
world:add(Heartbeat, Phase)
|
||||
world:set(Heartbeat, Event, RunService.Heartbeat)
|
||||
|
||||
world:add(PreSimulation, Phase)
|
||||
world:set(PreSimulation, Event, RunService.PreSimulation)
|
||||
world:add(PreSimulation, Phase)
|
||||
world:set(PreSimulation, Event, RunService.PreSimulation)
|
||||
|
||||
world:add(PreAnimation, Phase)
|
||||
world:set(PreAnimation, Event, RunService.PreAnimation)
|
||||
world:add(PreAnimation, Phase)
|
||||
world:set(PreAnimation, Event, RunService.PreAnimation)
|
||||
|
||||
for name, component in components do
|
||||
world:set(component, Name, name)
|
||||
end
|
||||
for name, component in components do
|
||||
world:set(component, Name, name)
|
||||
end
|
||||
|
||||
table.insert(jabby.public, {
|
||||
class_name = "World",
|
||||
name = "MyWorld",
|
||||
world = world,
|
||||
debug = Name,
|
||||
entities = {}
|
||||
})
|
||||
table.insert(jabby.public, {
|
||||
class_name = "World",
|
||||
name = "MyWorld",
|
||||
world = world,
|
||||
debug = Name,
|
||||
entities = {},
|
||||
})
|
||||
|
||||
jabby.public.updated = true
|
||||
scheduler = jabby.scheduler.create("scheduler")
|
||||
|
||||
jabby.public.updated = true
|
||||
scheduler = jabby.scheduler.create("scheduler")
|
||||
table.insert(jabby.public, scheduler)
|
||||
|
||||
table.insert(jabby.public, scheduler)
|
||||
return {
|
||||
phase = scheduler_phase_new,
|
||||
|
||||
return {
|
||||
phase = scheduler_phase_new,
|
||||
phases = {
|
||||
RenderStepped = RenderStepped,
|
||||
PreSimulation = PreSimulation,
|
||||
Heartbeat = Heartbeat,
|
||||
PreAnimation = PreAnimation,
|
||||
},
|
||||
|
||||
phases = {
|
||||
RenderStepped = RenderStepped,
|
||||
PreSimulation = PreSimulation,
|
||||
Heartbeat = Heartbeat,
|
||||
PreAnimation = PreAnimation
|
||||
},
|
||||
world = world,
|
||||
|
||||
world = world,
|
||||
components = {
|
||||
DependsOn = DependsOn,
|
||||
Disabled = Disabled,
|
||||
Phase = Phase,
|
||||
System = System,
|
||||
},
|
||||
|
||||
components = {
|
||||
DependsOn = DependsOn,
|
||||
Disabled = Disabled,
|
||||
Phase = Phase,
|
||||
System = System,
|
||||
},
|
||||
collect = {
|
||||
under_event = scheduler_collect_systems_under_event,
|
||||
all = scheduler_collect_systems_all,
|
||||
},
|
||||
|
||||
collect = {
|
||||
under_event = scheduler_collect_systems_under_event,
|
||||
all = scheduler_collect_systems_all
|
||||
},
|
||||
|
||||
systems = {
|
||||
new = scheduler_systems_new,
|
||||
begin = begin
|
||||
}
|
||||
}
|
||||
end
|
||||
systems = {
|
||||
new = scheduler_systems_new,
|
||||
begin = begin,
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
new = scheduler_new
|
||||
new = scheduler_new,
|
||||
}
|
||||
|
|
|
@ -21,58 +21,53 @@ local Player = cts.Player
|
|||
local Character = cts.Character
|
||||
|
||||
local function mobsMove(dt: number)
|
||||
local targets = {}
|
||||
for _, character in world:query(Character):with(Player):iter() do
|
||||
table.insert(targets, (character.PrimaryPart :: Part).Position)
|
||||
end
|
||||
local targets = {}
|
||||
for _, character in world:query(Character):with(Player):iter() do
|
||||
table.insert(targets, (character.PrimaryPart :: Part).Position)
|
||||
end
|
||||
|
||||
for mob, transform, v in world:query(Transform, Velocity):with(Mob):iter() do
|
||||
local cf = transform.new
|
||||
local p = cf.Position
|
||||
for mob, transform, v in world:query(Transform, Velocity):with(Mob):iter() do
|
||||
local cf = transform.new
|
||||
local p = cf.Position
|
||||
|
||||
local target
|
||||
local closest
|
||||
local target
|
||||
local closest
|
||||
|
||||
for _, pos in targets do
|
||||
local distance = (p - pos).Magnitude
|
||||
if not target or distance < closest then
|
||||
target = pos
|
||||
closest = distance
|
||||
end
|
||||
end
|
||||
for _, pos in targets do
|
||||
local distance = (p - pos).Magnitude
|
||||
if not target or distance < closest then
|
||||
target = pos
|
||||
closest = distance
|
||||
end
|
||||
end
|
||||
|
||||
if not target then
|
||||
continue
|
||||
end
|
||||
if not target then
|
||||
continue
|
||||
end
|
||||
|
||||
local moving = CFrame.new(p + (target - p).Unit * dt * v)
|
||||
transform.new = moving
|
||||
blink.UpdateTransform.FireAll(mob, moving)
|
||||
end
|
||||
local moving = CFrame.new(p + (target - p).Unit * dt * v)
|
||||
transform.new = moving
|
||||
blink.UpdateTransform.FireAll(mob, moving)
|
||||
end
|
||||
end
|
||||
|
||||
local throttle = interval(5)
|
||||
|
||||
local function spawnMobs()
|
||||
if throttle() then
|
||||
local p = Vector3.new(0, 5, 0)
|
||||
local cf = CFrame.new(p)
|
||||
local v = 5
|
||||
if throttle() then
|
||||
local p = Vector3.new(0, 5, 0)
|
||||
local cf = CFrame.new(p)
|
||||
local v = 5
|
||||
|
||||
local id = ref()
|
||||
:set(Velocity, v)
|
||||
:set(Transform, { new = cf })
|
||||
:add(Mob)
|
||||
.id()
|
||||
local id = ref():set(Velocity, v):set(Transform, { new = cf }):add(Mob).id()
|
||||
|
||||
blink.SpawnMob.FireAll(id, cf, v)
|
||||
end
|
||||
blink.SpawnMob.FireAll(id, cf, v)
|
||||
end
|
||||
end
|
||||
|
||||
return function(scheduler: std.Scheduler)
|
||||
local phases = scheduler.phases
|
||||
local system_new = scheduler.systems.new
|
||||
system_new(mobsMove, phases.Heartbeat)
|
||||
system_new(spawnMobs, phases.Heartbeat)
|
||||
|
||||
local phases = scheduler.phases
|
||||
local system_new = scheduler.systems.new
|
||||
system_new(mobsMove, phases.Heartbeat)
|
||||
system_new(spawnMobs, phases.Heartbeat)
|
||||
end
|
||||
|
|
|
@ -16,31 +16,28 @@ local world: std.World = std.world
|
|||
local conn = {}
|
||||
|
||||
local function players()
|
||||
for _, player in playersAdded do
|
||||
world:set(
|
||||
std.world:entity(),
|
||||
cts.Transform
|
||||
)
|
||||
for _, player in playersAdded do
|
||||
world:set(std.world:entity(), cts.Transform)
|
||||
|
||||
local e = ref(player.UserId):set(Player, player)
|
||||
local characterAdd = player.CharacterAdded
|
||||
conn[e.id()] = characterAdd:Connect(function(rig)
|
||||
while rig.Parent ~= workspace do
|
||||
task.wait()
|
||||
end
|
||||
e:set(Character, rig)
|
||||
end)
|
||||
end
|
||||
local e = ref(player.UserId):set(Player, player)
|
||||
local characterAdd = player.CharacterAdded
|
||||
conn[e.id()] = characterAdd:Connect(function(rig)
|
||||
while rig.Parent ~= workspace do
|
||||
task.wait()
|
||||
end
|
||||
e:set(Character, rig)
|
||||
end)
|
||||
end
|
||||
|
||||
for _, player in playersRemoved do
|
||||
local id = ref(player.UserId):clear().id()
|
||||
conn[id]:Disconnect()
|
||||
conn[id] = nil
|
||||
end
|
||||
for _, player in playersRemoved do
|
||||
local id = ref(player.UserId):clear().id()
|
||||
conn[id]:Disconnect()
|
||||
conn[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return function(scheduler: std.Scheduler)
|
||||
local phases = scheduler.phases
|
||||
local system_new = scheduler.systems.new
|
||||
system_new(players, phases.Heartbeat)
|
||||
local phases = scheduler.phases
|
||||
local system_new = scheduler.systems.new
|
||||
system_new(players, phases.Heartbeat)
|
||||
end
|
||||
|
|
|
@ -10,30 +10,30 @@ local Model = cts.Model
|
|||
local Transform = cts.Transform
|
||||
|
||||
local function move(dt: number)
|
||||
for _, transform, model in world:query(Transform, Model):iter() do
|
||||
local cf = transform.new
|
||||
if cf ~= transform.old then
|
||||
local origo = model.PrimaryPart.CFrame
|
||||
model.PrimaryPart.CFrame = origo:Lerp(cf, 1)
|
||||
transform.old = cf
|
||||
end
|
||||
end
|
||||
for _, transform, model in world:query(Transform, Model):iter() do
|
||||
local cf = transform.new
|
||||
if cf ~= transform.old then
|
||||
local origo = model.PrimaryPart.CFrame
|
||||
model.PrimaryPart.CFrame = origo:Lerp(cf, 1)
|
||||
transform.old = cf
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function syncTransforms()
|
||||
for _, id, cf in blink.UpdateTransform.Iter() do
|
||||
local e = ref("server-"..id)
|
||||
local transform = e:get(cts.Transform)
|
||||
if not transform then
|
||||
continue
|
||||
end
|
||||
transform.new = cf
|
||||
end
|
||||
for _, id, cf in blink.UpdateTransform.Iter() do
|
||||
local e = ref("server-" .. id)
|
||||
local transform = e:get(cts.Transform)
|
||||
if not transform then
|
||||
continue
|
||||
end
|
||||
transform.new = cf
|
||||
end
|
||||
end
|
||||
|
||||
return function(scheduler: std.Scheduler)
|
||||
local phases = scheduler.phases
|
||||
local system_new = scheduler.systems.new
|
||||
system_new(move, phases.Heartbeat)
|
||||
system_new(syncTransforms, phases.RenderStepped)
|
||||
local phases = scheduler.phases
|
||||
local system_new = scheduler.systems.new
|
||||
system_new(move, phases.Heartbeat)
|
||||
system_new(syncTransforms, phases.RenderStepped)
|
||||
end
|
||||
|
|
|
@ -6,26 +6,26 @@ local world = std.world
|
|||
local cts = std.components
|
||||
|
||||
local function syncMobs()
|
||||
for _, id, cf, vel in blink.SpawnMob.Iter() do
|
||||
local part = Instance.new("Part")
|
||||
part.Size = Vector3.one * 5
|
||||
part.BrickColor = BrickColor.Red()
|
||||
part.Anchored = true
|
||||
local model = Instance.new("Model")
|
||||
model.PrimaryPart = part
|
||||
part.Parent = model
|
||||
model.Parent = workspace
|
||||
for _, id, cf, vel in blink.SpawnMob.Iter() do
|
||||
local part = Instance.new("Part")
|
||||
part.Size = Vector3.one * 5
|
||||
part.BrickColor = BrickColor.Red()
|
||||
part.Anchored = true
|
||||
local model = Instance.new("Model")
|
||||
model.PrimaryPart = part
|
||||
part.Parent = model
|
||||
model.Parent = workspace
|
||||
|
||||
ref("server-"..id)
|
||||
:set(cts.Transform, { new = cf, old = cf })
|
||||
:set(cts.Velocity, vel)
|
||||
:set(cts.Model, model)
|
||||
:add(cts.Mob)
|
||||
end
|
||||
ref("server-" .. id)
|
||||
:set(cts.Transform, { new = cf, old = cf })
|
||||
:set(cts.Velocity, vel)
|
||||
:set(cts.Model, model)
|
||||
:add(cts.Mob)
|
||||
end
|
||||
end
|
||||
|
||||
return function(scheduler: std.Scheduler)
|
||||
local phases = scheduler.phases
|
||||
local system_new = scheduler.systems.new
|
||||
system_new(syncMobs, phases.RenderStepped)
|
||||
local phases = scheduler.phases
|
||||
local system_new = scheduler.systems.new
|
||||
system_new(syncMobs, phases.RenderStepped)
|
||||
end
|
||||
|
|
|
@ -23,20 +23,18 @@ print(`\{{pos.X}, {pos.Y}, {pos.Z}\}`)
|
|||
-- Overwrite the value of the Position component
|
||||
world:set(bob, Position, Vector3.new(40, 50, 60))
|
||||
|
||||
|
||||
local alice = world:entity()
|
||||
-- Create another named entity
|
||||
world:set(alice, Name, "Alice")
|
||||
world:set(alice, Position, Vector3.new(10, 20, 30))
|
||||
world:add(alice, Walking)
|
||||
|
||||
|
||||
-- Remove tag
|
||||
world:remove(alice, Walking)
|
||||
|
||||
-- Iterate all entities with Position
|
||||
for entity, p in world:query(Position) do
|
||||
print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
|
||||
print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
|
||||
end
|
||||
|
||||
-- Output:
|
||||
|
|
|
@ -11,67 +11,55 @@ local Moon = world:component()
|
|||
|
||||
local Vector3
|
||||
do
|
||||
Vector3 = {}
|
||||
Vector3.__index = Vector3
|
||||
Vector3 = {}
|
||||
Vector3.__index = Vector3
|
||||
|
||||
function Vector3.new(x, y, z)
|
||||
x = x or 0
|
||||
y = y or 0
|
||||
z = z or 0
|
||||
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
|
||||
end
|
||||
function Vector3.new(x, y, z)
|
||||
x = x or 0
|
||||
y = y or 0
|
||||
z = z or 0
|
||||
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
|
||||
end
|
||||
|
||||
function Vector3.__add(left, right)
|
||||
return Vector3.new(
|
||||
left.X + right.X,
|
||||
left.Y + right.Y,
|
||||
left.Z + right.Z
|
||||
)
|
||||
end
|
||||
function Vector3.__add(left, right)
|
||||
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
|
||||
end
|
||||
|
||||
function Vector3.__mul(left, right)
|
||||
if typeof(right) == "number" then
|
||||
return Vector3.new(
|
||||
left.X * right,
|
||||
left.Y * right,
|
||||
left.Z * right
|
||||
)
|
||||
end
|
||||
return Vector3.new(
|
||||
left.X * right.X,
|
||||
left.Y * right.Y,
|
||||
left.Z * right.Z
|
||||
)
|
||||
end
|
||||
function Vector3.__mul(left, right)
|
||||
if typeof(right) == "number" then
|
||||
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
|
||||
end
|
||||
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
|
||||
end
|
||||
|
||||
Vector3.one = Vector3.new(1, 1, 1)
|
||||
Vector3.zero = Vector3.new()
|
||||
Vector3.one = Vector3.new(1, 1, 1)
|
||||
Vector3.zero = Vector3.new()
|
||||
end
|
||||
|
||||
local function path(entity)
|
||||
local str = world:get(entity, Name)
|
||||
local parent
|
||||
while true do
|
||||
parent = world:parent(entity)
|
||||
if not parent then
|
||||
break
|
||||
end
|
||||
entity = parent
|
||||
str = world:get(parent, Name) .. "/" .. str
|
||||
end
|
||||
return str
|
||||
local str = world:get(entity, Name)
|
||||
local parent
|
||||
while true do
|
||||
parent = world:parent(entity)
|
||||
if not parent then
|
||||
break
|
||||
end
|
||||
entity = parent
|
||||
str = world:get(parent, Name) .. "/" .. str
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
local function iterate(entity, parent)
|
||||
local p = world:get(entity, Position)
|
||||
local actual = p + parent
|
||||
print(path(entity))
|
||||
print(`\{{actual.X}, {actual.Y}, {actual.Z}}`)
|
||||
local p = world:get(entity, Position)
|
||||
local actual = p + parent
|
||||
print(path(entity))
|
||||
print(`\{{actual.X}, {actual.Y}, {actual.Z}}`)
|
||||
|
||||
for child in world:query(pair(ChildOf, entity)) do
|
||||
--print(world:get(child, Name))
|
||||
iterate(child, actual)
|
||||
end
|
||||
for child in world:query(pair(ChildOf, entity)) do
|
||||
--print(world:get(child, Name))
|
||||
iterate(child, actual)
|
||||
end
|
||||
end
|
||||
|
||||
local sun = world:entity()
|
||||
|
@ -79,36 +67,35 @@ world:add(sun, Star)
|
|||
world:set(sun, Position, Vector3.one)
|
||||
world:set(sun, Name, "Sun")
|
||||
do
|
||||
local earth = world:entity()
|
||||
world:set(earth, Name, "Earth")
|
||||
world:add(earth, pair(ChildOf, sun))
|
||||
world:add(earth, Planet)
|
||||
world:set(earth, Position, Vector3.one * 3)
|
||||
local earth = world:entity()
|
||||
world:set(earth, Name, "Earth")
|
||||
world:add(earth, pair(ChildOf, sun))
|
||||
world:add(earth, Planet)
|
||||
world:set(earth, Position, Vector3.one * 3)
|
||||
|
||||
do
|
||||
local moon = world:entity()
|
||||
world:set(moon, Name, "Moon")
|
||||
world:add(moon, pair(ChildOf, earth))
|
||||
world:add(moon, Moon)
|
||||
world:set(moon, Position, Vector3.one * 0.1)
|
||||
do
|
||||
local moon = world:entity()
|
||||
world:set(moon, Name, "Moon")
|
||||
world:add(moon, pair(ChildOf, earth))
|
||||
world:add(moon, Moon)
|
||||
world:set(moon, Position, Vector3.one * 0.1)
|
||||
|
||||
print(`Child of Earth? {world:has(moon, pair(ChildOf, earth))}`)
|
||||
end
|
||||
print(`Child of Earth? {world:has(moon, pair(ChildOf, earth))}`)
|
||||
end
|
||||
|
||||
local venus = world:entity()
|
||||
world:set(venus, Name, "Venus")
|
||||
world:add(venus, pair(ChildOf, sun))
|
||||
world:add(venus, Planet)
|
||||
world:set(venus, Position, Vector3.one * 2)
|
||||
local venus = world:entity()
|
||||
world:set(venus, Name, "Venus")
|
||||
world:add(venus, pair(ChildOf, sun))
|
||||
world:add(venus, Planet)
|
||||
world:set(venus, Position, Vector3.one * 2)
|
||||
|
||||
local mercury = world:entity()
|
||||
world:set(mercury, Name, "Mercury")
|
||||
world:add(mercury, pair(ChildOf, sun))
|
||||
world:add(mercury, Planet)
|
||||
world:set(mercury, Position, Vector3.one)
|
||||
local mercury = world:entity()
|
||||
world:set(mercury, Name, "Mercury")
|
||||
world:add(mercury, pair(ChildOf, sun))
|
||||
world:add(mercury, Planet)
|
||||
world:set(mercury, Position, Vector3.one)
|
||||
|
||||
|
||||
iterate(sun, Vector3.zero)
|
||||
iterate(sun, Vector3.zero)
|
||||
end
|
||||
|
||||
-- Output:
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
local jecs = require("@jecs")
|
||||
local world = jecs.World.new()
|
||||
|
||||
local Model = world:component()
|
||||
|
||||
-- It is important to define hooks for the component before the component is ever used
|
||||
-- otherwise the hooks will never invoke!
|
||||
world:set(Model, jecs.OnRemove, function(entity)
|
||||
-- OnRemove is invoked before the component and its value is removed
|
||||
-- which provides a stable reference to the entity at deletion.
|
||||
-- This means that it is safe to retrieve the data inside of a hook
|
||||
local model = world:get(entity, Model)
|
||||
model:Destroy()
|
||||
end)
|
||||
|
||||
world:set(Model, jecs.OnSet, function(entity, model)
|
||||
-- OnSet is invoked after the data has been assigned.
|
||||
-- It also returns the data for faster access.
|
||||
-- There may be some logic to do some side effects on reassignments
|
||||
model:SetAttribute("entityId", entity)
|
||||
end)
|
||||
local jecs = require("@jecs")
|
||||
local world = jecs.World.new()
|
||||
|
||||
local Model = world:component()
|
||||
|
||||
-- It is important to define hooks for the component before the component is ever used
|
||||
-- otherwise the hooks will never invoke!
|
||||
world:set(Model, jecs.OnRemove, function(entity)
|
||||
-- OnRemove is invoked before the component and its value is removed
|
||||
-- which provides a stable reference to the entity at deletion.
|
||||
-- This means that it is safe to retrieve the data inside of a hook
|
||||
local model = world:get(entity, Model)
|
||||
model:Destroy()
|
||||
end)
|
||||
|
||||
world:set(Model, jecs.OnSet, function(entity, model)
|
||||
-- OnSet is invoked after the data has been assigned.
|
||||
-- It also returns the data for faster access.
|
||||
-- There may be some logic to do some side effects on reassignments
|
||||
model:SetAttribute("entityId", entity)
|
||||
end)
|
||||
|
|
|
@ -7,41 +7,29 @@ local Name = world:component()
|
|||
|
||||
local Vector3
|
||||
do
|
||||
Vector3 = {}
|
||||
Vector3.__index = Vector3
|
||||
Vector3 = {}
|
||||
Vector3.__index = Vector3
|
||||
|
||||
function Vector3.new(x, y, z)
|
||||
x = x or 0
|
||||
y = y or 0
|
||||
z = z or 0
|
||||
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
|
||||
end
|
||||
function Vector3.new(x, y, z)
|
||||
x = x or 0
|
||||
y = y or 0
|
||||
z = z or 0
|
||||
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
|
||||
end
|
||||
|
||||
function Vector3.__add(left, right)
|
||||
return Vector3.new(
|
||||
left.X + right.X,
|
||||
left.Y + right.Y,
|
||||
left.Z + right.Z
|
||||
)
|
||||
end
|
||||
function Vector3.__add(left, right)
|
||||
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
|
||||
end
|
||||
|
||||
function Vector3.__mul(left, right)
|
||||
if typeof(right) == "number" then
|
||||
return Vector3.new(
|
||||
left.X * right,
|
||||
left.Y * right,
|
||||
left.Z * right
|
||||
)
|
||||
end
|
||||
return Vector3.new(
|
||||
left.X * right.X,
|
||||
left.Y * right.Y,
|
||||
left.Z * right.Z
|
||||
)
|
||||
end
|
||||
function Vector3.__mul(left, right)
|
||||
if typeof(right) == "number" then
|
||||
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
|
||||
end
|
||||
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
|
||||
end
|
||||
|
||||
Vector3.one = Vector3.new(1, 1, 1)
|
||||
Vector3.zero = Vector3.new()
|
||||
Vector3.one = Vector3.new(1, 1, 1)
|
||||
Vector3.zero = Vector3.new()
|
||||
end
|
||||
|
||||
-- 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.
|
||||
for entity, p, v in world:query(Position, Velocity) do
|
||||
-- Iterate entities matching the query
|
||||
p.X += v.X
|
||||
p.Y += v.Y
|
||||
p.Z += v.Z
|
||||
-- Iterate entities matching the query
|
||||
p.X += v.X
|
||||
p.Y += v.Y
|
||||
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
|
||||
|
||||
-- Output:
|
||||
|
|
|
@ -2,179 +2,173 @@ local jecs = require("@jecs")
|
|||
|
||||
type World = jecs.WorldShim
|
||||
|
||||
type Tracker<T> = { track: (world: World, fn: (changes: {
|
||||
added: () -> () -> (number, T),
|
||||
removed: () -> () -> number,
|
||||
changed: () -> () -> (number, T, T)
|
||||
}) -> ()) -> ()
|
||||
type Tracker<T> = {
|
||||
track: (
|
||||
world: World,
|
||||
fn: (
|
||||
changes: {
|
||||
added: () -> () -> (number, T),
|
||||
removed: () -> () -> number,
|
||||
changed: () -> () -> (number, T, T),
|
||||
}
|
||||
) -> ()
|
||||
) -> (),
|
||||
}
|
||||
|
||||
local function diff(a, b)
|
||||
local size = 0
|
||||
for k, v in a do
|
||||
if b[k] ~= v then
|
||||
return true
|
||||
end
|
||||
size += 1
|
||||
end
|
||||
for k, v in b do
|
||||
size -= 1
|
||||
end
|
||||
local size = 0
|
||||
for k, v in a do
|
||||
if b[k] ~= v then
|
||||
return true
|
||||
end
|
||||
size += 1
|
||||
end
|
||||
for k, v in b do
|
||||
size -= 1
|
||||
end
|
||||
|
||||
if size ~= 0 then
|
||||
return true
|
||||
end
|
||||
if size ~= 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
return false
|
||||
end
|
||||
|
||||
type Entity<T> = number & { __nominal_type_dont_use: T }
|
||||
|
||||
local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
|
||||
local PreviousT = jecs.pair(jecs.Rest, T)
|
||||
local add = {}
|
||||
local added
|
||||
local removed
|
||||
local is_trivial
|
||||
local PreviousT = jecs.pair(jecs.Rest, T)
|
||||
local add = {}
|
||||
local added
|
||||
local removed
|
||||
local is_trivial
|
||||
|
||||
local function changes_added()
|
||||
added = true
|
||||
local q = world:query(T):without(PreviousT):drain()
|
||||
return function()
|
||||
local id, data = q.next()
|
||||
if not id then
|
||||
return nil
|
||||
end
|
||||
local function changes_added()
|
||||
added = true
|
||||
local q = world:query(T):without(PreviousT):drain()
|
||||
return function()
|
||||
local id, data = q.next()
|
||||
if not id then
|
||||
return nil
|
||||
end
|
||||
|
||||
is_trivial = typeof(data) ~= "table"
|
||||
is_trivial = typeof(data) ~= "table"
|
||||
|
||||
add[id] = data
|
||||
add[id] = data
|
||||
|
||||
return id, data
|
||||
end
|
||||
end
|
||||
return id, data
|
||||
end
|
||||
end
|
||||
|
||||
local function changes_changed()
|
||||
local q = world:query(T, PreviousT):drain()
|
||||
local function changes_changed()
|
||||
local q = world:query(T, PreviousT):drain()
|
||||
|
||||
return function()
|
||||
local id, new, old = q.next()
|
||||
while true do
|
||||
if not id then
|
||||
return nil
|
||||
end
|
||||
return function()
|
||||
local id, new, old = q.next()
|
||||
while true do
|
||||
if not id then
|
||||
return nil
|
||||
end
|
||||
|
||||
if not is_trivial then
|
||||
if diff(new, old) then
|
||||
break
|
||||
end
|
||||
elseif new ~= old then
|
||||
break
|
||||
end
|
||||
if not is_trivial then
|
||||
if diff(new, old) then
|
||||
break
|
||||
end
|
||||
elseif new ~= old then
|
||||
break
|
||||
end
|
||||
|
||||
id, new, old = q.next()
|
||||
end
|
||||
id, new, old = q.next()
|
||||
end
|
||||
|
||||
add[id] = new
|
||||
add[id] = new
|
||||
|
||||
return id, old, new
|
||||
end
|
||||
end
|
||||
return id, old, new
|
||||
end
|
||||
end
|
||||
|
||||
local function changes_removed()
|
||||
removed = true
|
||||
local function changes_removed()
|
||||
removed = true
|
||||
|
||||
local q = world:query(PreviousT):without(T):drain()
|
||||
return function()
|
||||
local id = q.next()
|
||||
if id then
|
||||
world:remove(id, PreviousT)
|
||||
end
|
||||
return id
|
||||
end
|
||||
end
|
||||
local q = world:query(PreviousT):without(T):drain()
|
||||
return function()
|
||||
local id = q.next()
|
||||
if id then
|
||||
world:remove(id, PreviousT)
|
||||
end
|
||||
return id
|
||||
end
|
||||
end
|
||||
|
||||
local changes = {
|
||||
added = changes_added,
|
||||
changed = changes_changed,
|
||||
removed = changes_removed,
|
||||
}
|
||||
local changes = {
|
||||
added = changes_added,
|
||||
changed = changes_changed,
|
||||
removed = changes_removed,
|
||||
}
|
||||
|
||||
local function track(fn)
|
||||
added = false
|
||||
removed = false
|
||||
local function track(fn)
|
||||
added = false
|
||||
removed = false
|
||||
|
||||
fn(changes)
|
||||
fn(changes)
|
||||
|
||||
if not added then
|
||||
for _ in changes_added() do
|
||||
end
|
||||
end
|
||||
if not added then
|
||||
for _ in changes_added() do
|
||||
end
|
||||
end
|
||||
|
||||
if not removed then
|
||||
for _ in changes_removed() do
|
||||
end
|
||||
end
|
||||
if not removed then
|
||||
for _ in changes_removed() do
|
||||
end
|
||||
end
|
||||
|
||||
for e, data in add do
|
||||
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
|
||||
end
|
||||
end
|
||||
for e, data in add do
|
||||
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
|
||||
end
|
||||
end
|
||||
|
||||
local tracker = { track = track }
|
||||
local tracker = { track = track }
|
||||
|
||||
return tracker
|
||||
return tracker
|
||||
end
|
||||
|
||||
local Vector3
|
||||
do
|
||||
Vector3 = {}
|
||||
Vector3.__index = Vector3
|
||||
Vector3 = {}
|
||||
Vector3.__index = Vector3
|
||||
|
||||
function Vector3.new(x, y, z)
|
||||
x = x or 0
|
||||
y = y or 0
|
||||
z = z or 0
|
||||
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
|
||||
end
|
||||
function Vector3.new(x, y, z)
|
||||
x = x or 0
|
||||
y = y or 0
|
||||
z = z or 0
|
||||
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
|
||||
end
|
||||
|
||||
function Vector3.__add(left, right)
|
||||
return Vector3.new(
|
||||
left.X + right.X,
|
||||
left.Y + right.Y,
|
||||
left.Z + right.Z
|
||||
)
|
||||
end
|
||||
function Vector3.__add(left, right)
|
||||
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
|
||||
end
|
||||
|
||||
function Vector3.__mul(left, right)
|
||||
if typeof(right) == "number" then
|
||||
return Vector3.new(
|
||||
left.X * right,
|
||||
left.Y * right,
|
||||
left.Z * right
|
||||
)
|
||||
end
|
||||
return Vector3.new(
|
||||
left.X * right.X,
|
||||
left.Y * right.Y,
|
||||
left.Z * right.Z
|
||||
)
|
||||
end
|
||||
function Vector3.__mul(left, right)
|
||||
if typeof(right) == "number" then
|
||||
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
|
||||
end
|
||||
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
|
||||
end
|
||||
|
||||
Vector3.one = Vector3.new(1, 1, 1)
|
||||
Vector3.zero = Vector3.new()
|
||||
Vector3.one = Vector3.new(1, 1, 1)
|
||||
Vector3.zero = Vector3.new()
|
||||
end
|
||||
|
||||
local world = jecs.World.new()
|
||||
local Name = world:component()
|
||||
|
||||
local function named(ctr, name)
|
||||
local e = ctr(world)
|
||||
world:set(e, Name, name)
|
||||
return e
|
||||
local e = ctr(world)
|
||||
world:set(e, Name, name)
|
||||
return e
|
||||
end
|
||||
local function name(e)
|
||||
return world:get(e, Name)
|
||||
return world:get(e, Name)
|
||||
end
|
||||
|
||||
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))
|
||||
|
||||
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
|
||||
for e, p in changes.added() do
|
||||
print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
|
||||
end
|
||||
-- added queries for every entity with a new Position component
|
||||
for e, p in changes.added() do
|
||||
print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
|
||||
end
|
||||
|
||||
-- changed queries for entities who's changed their data since
|
||||
-- last was it tracked
|
||||
for _ in changes.changed() do
|
||||
print([[This won't print because it is the first time
|
||||
-- changed queries for entities who's changed their data since
|
||||
-- last was it tracked
|
||||
for _ in changes.changed() do
|
||||
print([[This won't print because it is the first time
|
||||
we are tracking the Position component]])
|
||||
end
|
||||
end
|
||||
|
||||
-- removed queries for entities who's removed their Position component
|
||||
-- since last it was tracked
|
||||
for _ in changes.removed() do
|
||||
print([[This won't print because it is the first time
|
||||
-- removed queries for entities who's removed their Position component
|
||||
-- since last it was tracked
|
||||
for _ in changes.removed() do
|
||||
print([[This won't print because it is the first time
|
||||
we are tracking the Position component]])
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
world:set(e1, Position, Vector3.new(1, 1, 2) * 999)
|
||||
|
||||
PositionTracker.track(function(changes)
|
||||
for e, p in changes.added() do
|
||||
print([[This won't never print no Position component was added
|
||||
for e, p in changes.added() do
|
||||
print([[This won't never print no Position component was added
|
||||
since last time we tracked]])
|
||||
end
|
||||
end
|
||||
|
||||
for e, old, new in changes.changed() do
|
||||
print(`{name(e)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
|
||||
end
|
||||
for e, old, new in changes.changed() do
|
||||
print(`{name(e)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
|
||||
end
|
||||
|
||||
-- If you don't call e.g. changes.removed() then it will automatically drain its iterator and stage their changes.
|
||||
-- This ensures you will not have any off-by-one frame errors.
|
||||
-- If you don't call e.g. changes.removed() then it will automatically drain its iterator and stage their changes.
|
||||
-- This ensures you will not have any off-by-one frame errors.
|
||||
end)
|
||||
|
||||
world:remove(e2, Position)
|
||||
|
||||
PositionTracker.track(function(changes)
|
||||
for e in changes.removed() do
|
||||
print(`Position was removed from {name(e)}`)
|
||||
end
|
||||
for e in changes.removed() do
|
||||
print(`Position was removed from {name(e)}`)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Output:
|
||||
|
|
|
@ -1,130 +1,125 @@
|
|||
local jecs = require("@jecs")
|
||||
local pair = jecs.pair
|
||||
local ChildOf = jecs.ChildOf
|
||||
local __ = jecs.Wildcard
|
||||
local Name = jecs.Name
|
||||
local world = jecs.World.new()
|
||||
|
||||
type Id<T=nil> = number & {__T: T}
|
||||
local Voxel = world:component() :: Id
|
||||
local Position = world:component() :: Id<Vector3>
|
||||
local Perception = world:component() :: Id<{
|
||||
range: number,
|
||||
fov: number,
|
||||
dir: Vector3
|
||||
}>
|
||||
local PrimaryPart = world:component() :: Id<Part>
|
||||
|
||||
local local_player = game:GetService("Players").LocalPlayer
|
||||
|
||||
local function distance(a: Vector3, b: Vector3)
|
||||
return (b - a).Magnitude
|
||||
end
|
||||
|
||||
local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, fov_angle: number)
|
||||
local to_target = b - a
|
||||
|
||||
local forward_xz = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit
|
||||
local to_target_xz = Vector3.new(to_target.X, 0, to_target.Z).Unit
|
||||
|
||||
local angle_to_target = math.deg(math.atan2(to_target_xz.Z, to_target_xz.X))
|
||||
local forward_angle = math.deg(math.atan2(forward_xz.Z, forward_xz.X))
|
||||
|
||||
local angle_difference = math.abs(forward_angle - angle_to_target)
|
||||
|
||||
if angle_difference > 180 then
|
||||
angle_difference = 360 - angle_difference
|
||||
end
|
||||
|
||||
return angle_difference <= (fov_angle / 2)
|
||||
end
|
||||
|
||||
local map = {}
|
||||
local grid = 50
|
||||
|
||||
local function add_to_voxel(source: number, position: Vector3,
|
||||
prev_voxel_id: number?)
|
||||
|
||||
local hash = position // grid
|
||||
local voxel_id = map[hash]
|
||||
if not voxel_id then
|
||||
voxel_id = world:entity()
|
||||
world:add(voxel_id, Voxel)
|
||||
world:set(voxel_id, Position, hash)
|
||||
map[hash] = voxel_id
|
||||
end
|
||||
if prev_voxel_id ~= nil then
|
||||
world:remove(source, pair(ChildOf, prev_voxel_id))
|
||||
end
|
||||
world:add(source, pair(ChildOf, voxel_id))
|
||||
end
|
||||
|
||||
local function reconcile_client_owned_assembly_to_voxel(dt: number)
|
||||
for e, part, position in world:query(PrimaryPart, Position) do
|
||||
local p = part.Position
|
||||
if p ~= position then
|
||||
world:set(e, Position, p)
|
||||
local voxel_id = world:target(e, ChildOf, 0)
|
||||
if map[p // grid] == voxel_id then
|
||||
continue
|
||||
end
|
||||
|
||||
add_to_voxel(e, p, voxel_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function update_camera_direction(dt: number)
|
||||
for _, perception in world:query(Perception) do
|
||||
perception.dir = workspace.CurrentCamera.CFrame.LookVector
|
||||
end
|
||||
end
|
||||
|
||||
local function perceive_enemies(dt: number)
|
||||
local it = world:query(Perception, Position, PrimaryPart)
|
||||
-- There is only going to be one entity matching the query
|
||||
local e, self_perception, self_position, self_primary_part = it()
|
||||
|
||||
local voxel_id = map[self_primary_part.Position // grid]
|
||||
local nearby_entities_query = world:query(Position, pair(ChildOf, voxel_id))
|
||||
|
||||
for enemy, target_position in nearby_entities_query do
|
||||
if distance(self_position, target_position) > self_perception.range then
|
||||
continue
|
||||
end
|
||||
|
||||
if is_in_fov(self_position, target_position,
|
||||
self_perception.dir, self_perception.fov) then
|
||||
|
||||
local p = target_position
|
||||
print(`Entity {world:get(e, Name)} can see target {world:get(enemy, Name)} at ({p.X}, {p.Y}, {p.Z})`)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local player = world:entity()
|
||||
world:set(player, Perception, {
|
||||
range = 100,
|
||||
fov = 90,
|
||||
dir = Vector3.new(1, 0, 0)
|
||||
})
|
||||
world:set(player, Name, "LocalPlayer")
|
||||
local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
|
||||
world:set(player, PrimaryPart, primary_part)
|
||||
world:set(player, Position, Vector3.zero)
|
||||
|
||||
local enemy = world:entity()
|
||||
world:set(enemy, Name, "Enemy $1")
|
||||
world:set(enemy, Position, Vector3.new(50, 0, 20))
|
||||
|
||||
|
||||
add_to_voxel(player, primary_part.Position)
|
||||
add_to_voxel(enemy, world)
|
||||
|
||||
local dt = 1/60
|
||||
reconcile_client_owned_assembly_to_voxel(dt)
|
||||
update_camera_direction(dt)
|
||||
perceive_enemies(dt)
|
||||
|
||||
-- Output:
|
||||
-- LocalPlayer can see target Enemy $1
|
||||
local jecs = require("@jecs")
|
||||
local pair = jecs.pair
|
||||
local ChildOf = jecs.ChildOf
|
||||
local __ = jecs.Wildcard
|
||||
local Name = jecs.Name
|
||||
local world = jecs.World.new()
|
||||
|
||||
type Id<T = nil> = number & { __T: T }
|
||||
local Voxel = world:component() :: Id
|
||||
local Position = world:component() :: Id<Vector3>
|
||||
local Perception = world:component() :: Id<{
|
||||
range: number,
|
||||
fov: number,
|
||||
dir: Vector3,
|
||||
}>
|
||||
local PrimaryPart = world:component() :: Id<Part>
|
||||
|
||||
local local_player = game:GetService("Players").LocalPlayer
|
||||
|
||||
local function distance(a: Vector3, b: Vector3)
|
||||
return (b - a).Magnitude
|
||||
end
|
||||
|
||||
local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, fov_angle: number)
|
||||
local to_target = b - a
|
||||
|
||||
local forward_xz = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit
|
||||
local to_target_xz = Vector3.new(to_target.X, 0, to_target.Z).Unit
|
||||
|
||||
local angle_to_target = math.deg(math.atan2(to_target_xz.Z, to_target_xz.X))
|
||||
local forward_angle = math.deg(math.atan2(forward_xz.Z, forward_xz.X))
|
||||
|
||||
local angle_difference = math.abs(forward_angle - angle_to_target)
|
||||
|
||||
if angle_difference > 180 then
|
||||
angle_difference = 360 - angle_difference
|
||||
end
|
||||
|
||||
return angle_difference <= (fov_angle / 2)
|
||||
end
|
||||
|
||||
local map = {}
|
||||
local grid = 50
|
||||
|
||||
local function add_to_voxel(source: number, position: Vector3, prev_voxel_id: number?)
|
||||
local hash = position // grid
|
||||
local voxel_id = map[hash]
|
||||
if not voxel_id then
|
||||
voxel_id = world:entity()
|
||||
world:add(voxel_id, Voxel)
|
||||
world:set(voxel_id, Position, hash)
|
||||
map[hash] = voxel_id
|
||||
end
|
||||
if prev_voxel_id ~= nil then
|
||||
world:remove(source, pair(ChildOf, prev_voxel_id))
|
||||
end
|
||||
world:add(source, pair(ChildOf, voxel_id))
|
||||
end
|
||||
|
||||
local function reconcile_client_owned_assembly_to_voxel(dt: number)
|
||||
for e, part, position in world:query(PrimaryPart, Position) do
|
||||
local p = part.Position
|
||||
if p ~= position then
|
||||
world:set(e, Position, p)
|
||||
local voxel_id = world:target(e, ChildOf, 0)
|
||||
if map[p // grid] == voxel_id then
|
||||
continue
|
||||
end
|
||||
|
||||
add_to_voxel(e, p, voxel_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function update_camera_direction(dt: number)
|
||||
for _, perception in world:query(Perception) do
|
||||
perception.dir = workspace.CurrentCamera.CFrame.LookVector
|
||||
end
|
||||
end
|
||||
|
||||
local function perceive_enemies(dt: number)
|
||||
local it = world:query(Perception, Position, PrimaryPart)
|
||||
-- There is only going to be one entity matching the query
|
||||
local e, self_perception, self_position, self_primary_part = it()
|
||||
|
||||
local voxel_id = map[self_primary_part.Position // grid]
|
||||
local nearby_entities_query = world:query(Position, pair(ChildOf, voxel_id))
|
||||
|
||||
for enemy, target_position in nearby_entities_query do
|
||||
if distance(self_position, target_position) > self_perception.range then
|
||||
continue
|
||||
end
|
||||
|
||||
if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then
|
||||
local p = target_position
|
||||
print(`Entity {world:get(e, Name)} can see target {world:get(enemy, Name)} at ({p.X}, {p.Y}, {p.Z})`)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local player = world:entity()
|
||||
world:set(player, Perception, {
|
||||
range = 100,
|
||||
fov = 90,
|
||||
dir = Vector3.new(1, 0, 0),
|
||||
})
|
||||
world:set(player, Name, "LocalPlayer")
|
||||
local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
|
||||
world:set(player, PrimaryPart, primary_part)
|
||||
world:set(player, Position, Vector3.zero)
|
||||
|
||||
local enemy = world:entity()
|
||||
world:set(enemy, Name, "Enemy $1")
|
||||
world:set(enemy, Position, Vector3.new(50, 0, 20))
|
||||
|
||||
add_to_voxel(player, primary_part.Position)
|
||||
add_to_voxel(enemy, world)
|
||||
|
||||
local dt = 1 / 60
|
||||
reconcile_client_owned_assembly_to_voxel(dt)
|
||||
update_camera_direction(dt)
|
||||
perceive_enemies(dt)
|
||||
|
||||
-- Output:
|
||||
-- LocalPlayer can see target Enemy $1
|
||||
|
|
|
@ -4,12 +4,12 @@ local world = jecs.World.new()
|
|||
local Name = world:component()
|
||||
|
||||
local function named(ctr, name)
|
||||
local e = ctr(world)
|
||||
world:set(e, Name, name)
|
||||
return e
|
||||
local e = ctr(world)
|
||||
world:set(e, Name, name)
|
||||
return e
|
||||
end
|
||||
local function name(e)
|
||||
return world:get(e, Name)
|
||||
return world:get(e, Name)
|
||||
end
|
||||
|
||||
local Eats = world:component()
|
||||
|
@ -27,9 +27,9 @@ local __ = jecs.Wildcard
|
|||
|
||||
-- Create a query that matches edible components
|
||||
for entity, amount in world:query(pair(Eats, __)) do
|
||||
-- Iterate the query
|
||||
local food = world:target(entity, Eats)
|
||||
print(`{name(entity)} eats {amount} {name(food)}`)
|
||||
-- Iterate the query
|
||||
local food = world:target(entity, Eats)
|
||||
print(`{name(entity)} eats {amount} {name(food)}`)
|
||||
end
|
||||
|
||||
-- Output:
|
||||
|
|
100
mirror/init.luau
100
mirror/init.luau
|
@ -6,10 +6,10 @@
|
|||
type i53 = number
|
||||
type i24 = number
|
||||
|
||||
type Ty = {i53}
|
||||
type Ty = { i53 }
|
||||
type ArchetypeId = number
|
||||
|
||||
type Column = {any}
|
||||
type Column = { any }
|
||||
|
||||
type Archetype = {
|
||||
id: number,
|
||||
|
@ -21,8 +21,8 @@ type Archetype = {
|
|||
},
|
||||
types: Ty,
|
||||
type: string | number,
|
||||
entities: {number},
|
||||
columns: {Column},
|
||||
entities: { number },
|
||||
columns: { Column },
|
||||
records: {},
|
||||
}
|
||||
|
||||
|
@ -31,12 +31,12 @@ type Record = {
|
|||
row: number,
|
||||
}
|
||||
|
||||
type EntityIndex = {[i24]: Record}
|
||||
type ComponentIndex = {[i24]: ArchetypeMap}
|
||||
type EntityIndex = { [i24]: Record }
|
||||
type ComponentIndex = { [i24]: ArchetypeMap }
|
||||
|
||||
type ArchetypeRecord = number
|
||||
type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number}
|
||||
type Archetypes = {[ArchetypeId]: Archetype}
|
||||
type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord }, size: number }
|
||||
type Archetypes = { [ArchetypeId]: Archetype }
|
||||
|
||||
type ArchetypeDiff = {
|
||||
added: Ty,
|
||||
|
@ -134,7 +134,7 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet
|
|||
local archetypesMap = componentIndex[destinationId]
|
||||
|
||||
if not archetypesMap then
|
||||
archetypesMap = {size = 0, sparse = {}}
|
||||
archetypesMap = { size = 0, sparse = {} }
|
||||
componentIndex[destinationId] = archetypesMap
|
||||
end
|
||||
|
||||
|
@ -143,27 +143,27 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet
|
|||
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 id = world.nextArchetypeId + 1
|
||||
world.nextArchetypeId = id
|
||||
|
||||
local length = #types
|
||||
local columns = table.create(length) :: {any}
|
||||
local columns = table.create(length) :: { any }
|
||||
|
||||
for index in types do
|
||||
columns[index] = {}
|
||||
end
|
||||
|
||||
local archetype = {
|
||||
columns = columns;
|
||||
edges = {};
|
||||
entities = {};
|
||||
id = id;
|
||||
records = {};
|
||||
type = ty;
|
||||
types = types;
|
||||
columns = columns,
|
||||
edges = {},
|
||||
entities = {},
|
||||
id = id,
|
||||
records = {},
|
||||
type = ty,
|
||||
types = types,
|
||||
}
|
||||
world.archetypeIndex[ty] = archetype
|
||||
world.archetypes[id] = archetype
|
||||
|
@ -178,17 +178,17 @@ local World = {}
|
|||
World.__index = World
|
||||
function World.new()
|
||||
local self = setmetatable({
|
||||
archetypeIndex = {};
|
||||
archetypes = {};
|
||||
componentIndex = {};
|
||||
entityIndex = {};
|
||||
archetypeIndex = {},
|
||||
archetypes = {},
|
||||
componentIndex = {},
|
||||
entityIndex = {},
|
||||
hooks = {
|
||||
[ON_ADD] = {};
|
||||
};
|
||||
nextArchetypeId = 0;
|
||||
nextComponentId = 0;
|
||||
nextEntityId = 0;
|
||||
ROOT_ARCHETYPE = (nil :: any) :: Archetype;
|
||||
[ON_ADD] = {},
|
||||
},
|
||||
nextArchetypeId = 0,
|
||||
nextComponentId = 0,
|
||||
nextEntityId = 0,
|
||||
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
||||
}, World)
|
||||
return self
|
||||
end
|
||||
|
@ -197,21 +197,21 @@ local function emit(world, eventDescription)
|
|||
local event = eventDescription.event
|
||||
|
||||
table.insert(world.hooks[event], {
|
||||
archetype = eventDescription.archetype;
|
||||
ids = eventDescription.ids;
|
||||
offset = eventDescription.offset;
|
||||
otherArchetype = eventDescription.otherArchetype;
|
||||
archetype = eventDescription.archetype,
|
||||
ids = eventDescription.ids,
|
||||
offset = eventDescription.offset,
|
||||
otherArchetype = eventDescription.otherArchetype,
|
||||
})
|
||||
end
|
||||
|
||||
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
|
||||
if #added > 0 then
|
||||
emit(world, {
|
||||
archetype = archetype;
|
||||
event = ON_ADD;
|
||||
ids = added;
|
||||
offset = row;
|
||||
otherArchetype = otherArchetype;
|
||||
archetype = archetype,
|
||||
event = ON_ADD,
|
||||
ids = added,
|
||||
offset = row,
|
||||
otherArchetype = otherArchetype,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
@ -232,7 +232,7 @@ local function ensureArchetype(world: World, types, prev)
|
|||
return archetypeOf(world, types, prev)
|
||||
end
|
||||
|
||||
local function findInsert(types: {i53}, toAdd: i53)
|
||||
local function findInsert(types: { i53 }, toAdd: i53)
|
||||
for i, id in types do
|
||||
if id == toAdd then
|
||||
return -1
|
||||
|
@ -326,7 +326,7 @@ function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
|
|||
if #to.types > 0 then
|
||||
-- When there is no previous archetype it should create the archetype
|
||||
newEntity(entityId, record, to)
|
||||
onNotifyAdd(world, to, from, record.row, {componentId})
|
||||
onNotifyAdd(world, to, from, record.row, { componentId })
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -401,8 +401,8 @@ local function noop(_self: Query, ...: i53): () -> (number, ...any)
|
|||
end
|
||||
|
||||
local EmptyQuery = {
|
||||
__iter = noop;
|
||||
without = noop;
|
||||
__iter = noop,
|
||||
without = noop,
|
||||
}
|
||||
EmptyQuery.__index = EmptyQuery
|
||||
setmetatable(EmptyQuery, EmptyQuery)
|
||||
|
@ -418,7 +418,7 @@ function World.query(world: World, ...: i53): Query
|
|||
local compatibleArchetypes = {}
|
||||
local length = 0
|
||||
|
||||
local components = {...}
|
||||
local components = { ... }
|
||||
local archetypes = world.archetypes
|
||||
local queryLength = #components
|
||||
|
||||
|
@ -456,7 +456,7 @@ function World.query(world: World, ...: i53): Query
|
|||
end
|
||||
|
||||
length += 1
|
||||
compatibleArchetypes[length] = {archetype, indices}
|
||||
compatibleArchetypes[length] = { archetype, indices }
|
||||
end
|
||||
|
||||
local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
|
||||
|
@ -468,7 +468,7 @@ function World.query(world: World, ...: i53): Query
|
|||
preparedQuery.__index = preparedQuery
|
||||
|
||||
function preparedQuery:without(...)
|
||||
local withoutComponents = {...}
|
||||
local withoutComponents = { ... }
|
||||
for i = #compatibleArchetypes, 1, -1 do
|
||||
local archetype = compatibleArchetypes[i][1]
|
||||
local records = archetype.records
|
||||
|
@ -599,7 +599,7 @@ function World.delete(world: World, entityId: i53)
|
|||
end
|
||||
|
||||
function World.observer(world: World, ...)
|
||||
local componentIds = {...}
|
||||
local componentIds = { ... }
|
||||
local idsCount = #componentIds
|
||||
local hooks = world.hooks
|
||||
|
||||
|
@ -647,13 +647,13 @@ function World.observer(world: World, ...)
|
|||
|
||||
return archetype.entities[row], unpack(queryOutput, 1, idsCount)
|
||||
end
|
||||
end;
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return table.freeze({
|
||||
World = World;
|
||||
ON_ADD = ON_ADD;
|
||||
ON_REMOVE = ON_REMOVE;
|
||||
ON_SET = ON_SET;
|
||||
World = World,
|
||||
ON_ADD = ON_ADD,
|
||||
ON_REMOVE = ON_REMOVE,
|
||||
ON_SET = ON_SET,
|
||||
})
|
||||
|
|
4
rgb.luau
4
rgb.luau
|
@ -16,7 +16,7 @@ return {
|
|||
end,
|
||||
|
||||
yellow = function(s: any)
|
||||
return `\27[33;1m{s}\27[0m`
|
||||
return `\27[33;1m{s}\27[0m`
|
||||
end,
|
||||
|
||||
red_highlight = function(s: any)
|
||||
|
@ -30,4 +30,4 @@ return {
|
|||
gray = function(s: any)
|
||||
return `\27[30;1m{s}\27[0m`
|
||||
end,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tools]
|
||||
wally = "upliftgames/wally@0.3.2"
|
||||
rojo = "rojo-rbx/rojo@7.4.1"
|
||||
stylua = "johnnymorganz/stylua@0.19.1"
|
||||
selene = "kampfkarren/selene@0.26.1"
|
||||
rojo = "rojo-rbx/rojo@7.4.4"
|
||||
stylua = "johnnymorganz/stylua@0.20.0"
|
||||
selene = "kampfkarren/selene@0.27.1"
|
||||
Blink = "1Axen/Blink@0.14.1"
|
||||
|
|
|
@ -972,8 +972,6 @@ local function world_cleanup(world)
|
|||
world.archetypeIndex = new_archetype_map
|
||||
end
|
||||
|
||||
|
||||
|
||||
local world_delete: (world: World, entity: i53, destruct: boolean?) -> ()
|
||||
do
|
||||
function world_delete(world: World, entity: i53, destruct: boolean?)
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
column_width = 120
|
||||
line_endings = "Unix"
|
||||
indent_type = "Tabs"
|
||||
indent_width = 4
|
||||
quote_style = "ForceDouble"
|
||||
call_parentheses = "Always"
|
||||
collapse_simple_statement = "Never"
|
||||
|
||||
[sort_requires]
|
||||
enabled = true
|
||||
|
|
208
test/btree.luau
208
test/btree.luau
|
@ -1,152 +1,152 @@
|
|||
-- original author @centauri
|
||||
local bt
|
||||
do
|
||||
local FAILURE = 0
|
||||
local SUCCESS = 1
|
||||
local RUNNING = 2
|
||||
|
||||
local FAILURE = 0
|
||||
local SUCCESS = 1
|
||||
local RUNNING = 2
|
||||
|
||||
local function SEQUENCE(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == FAILURE or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return SUCCESS
|
||||
end
|
||||
end
|
||||
local function FALLBACK(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == SUCCESS or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return FAILURE
|
||||
end
|
||||
end
|
||||
bt = {
|
||||
SEQUENCE = SEQUENCE,
|
||||
FALLBACK = FALLBACK,
|
||||
RUNNING = RUNNING,
|
||||
SUCCESS = SUCCESS,
|
||||
FAILURE = FAILURE,
|
||||
}
|
||||
local function SEQUENCE(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == FAILURE or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return SUCCESS
|
||||
end
|
||||
end
|
||||
local function FALLBACK(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == SUCCESS or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return FAILURE
|
||||
end
|
||||
end
|
||||
bt = {
|
||||
SEQUENCE = SEQUENCE,
|
||||
FALLBACK = FALLBACK,
|
||||
RUNNING = RUNNING,
|
||||
SUCCESS = SUCCESS,
|
||||
FAILURE = FAILURE,
|
||||
}
|
||||
end
|
||||
|
||||
local SEQUENCE, FALLBACK = bt.SEQUENCE, bt.FALLBACK
|
||||
local RUNNING, SUCCESS, FAILURE = bt.FAILURE, bt.SUCCESS, bt.FAILURE
|
||||
|
||||
local btree = FALLBACK {
|
||||
SEQUENCE {
|
||||
function()
|
||||
return 1
|
||||
end,
|
||||
local btree = FALLBACK({
|
||||
SEQUENCE({
|
||||
function()
|
||||
return 1
|
||||
end,
|
||||
|
||||
function()
|
||||
return 0
|
||||
end
|
||||
},
|
||||
SEQUENCE {
|
||||
function()
|
||||
print(3)
|
||||
local start = os.clock()
|
||||
local now = os.clock()
|
||||
while os.clock() - now < 4 do
|
||||
print("yielding")
|
||||
coroutine.yield()
|
||||
end
|
||||
return 0
|
||||
end
|
||||
},
|
||||
function()
|
||||
return 1
|
||||
end
|
||||
}
|
||||
function()
|
||||
return 0
|
||||
end,
|
||||
}),
|
||||
SEQUENCE({
|
||||
function()
|
||||
print(3)
|
||||
local start = os.clock()
|
||||
local now = os.clock()
|
||||
while os.clock() - now < 4 do
|
||||
print("yielding")
|
||||
coroutine.yield()
|
||||
end
|
||||
return 0
|
||||
end,
|
||||
}),
|
||||
function()
|
||||
return 1
|
||||
end,
|
||||
})
|
||||
|
||||
function wait(seconds)
|
||||
local start = os.clock()
|
||||
while os.clock() - start < seconds do end
|
||||
return os.clock() - start
|
||||
local start = os.clock()
|
||||
while os.clock() - start < seconds do
|
||||
end
|
||||
return os.clock() - start
|
||||
end
|
||||
|
||||
local function panic(str)
|
||||
-- We don't want to interrupt the loop when we error
|
||||
coroutine.resume(coroutine.create(function() error(str) end))
|
||||
-- We don't want to interrupt the loop when we error
|
||||
coroutine.resume(coroutine.create(function()
|
||||
error(str)
|
||||
end))
|
||||
end
|
||||
|
||||
local jecs = require("@jecs")
|
||||
local world = jecs.World.new()
|
||||
|
||||
local function Scheduler(world, ...)
|
||||
local systems = { ... }
|
||||
local systemsNames = {}
|
||||
local N = #systems
|
||||
local system
|
||||
local dt
|
||||
local systems = { ... }
|
||||
local systemsNames = {}
|
||||
local N = #systems
|
||||
local system
|
||||
local dt
|
||||
|
||||
for i, module in systems do
|
||||
local sys = if typeof(module) == "function" then module else require(module)
|
||||
systems[i] = sys
|
||||
local file, line = debug.info(2, "sl")
|
||||
systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
|
||||
end
|
||||
for i, module in systems do
|
||||
local sys = if typeof(module) == "function" then module else require(module)
|
||||
systems[i] = sys
|
||||
local file, line = debug.info(2, "sl")
|
||||
systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
|
||||
end
|
||||
|
||||
local function run()
|
||||
local name = systemsNames[system]
|
||||
local function run()
|
||||
local name = systemsNames[system]
|
||||
|
||||
--debug.profilebegin(name)
|
||||
--debug.setmemorycategory(name)
|
||||
system(world, dt)
|
||||
--debug.profileend()
|
||||
end
|
||||
--debug.profilebegin(name)
|
||||
--debug.setmemorycategory(name)
|
||||
system(world, dt)
|
||||
--debug.profileend()
|
||||
end
|
||||
|
||||
local function loop(sinceLastFrame)
|
||||
--debug.profilebegin("loop()")
|
||||
local start = os.clock()
|
||||
for i = N, 1, -1 do
|
||||
system = systems[i]
|
||||
local function loop(sinceLastFrame)
|
||||
--debug.profilebegin("loop()")
|
||||
local start = os.clock()
|
||||
for i = N, 1, -1 do
|
||||
system = systems[i]
|
||||
|
||||
dt = sinceLastFrame
|
||||
dt = sinceLastFrame
|
||||
|
||||
local didNotYield, why = xpcall(function()
|
||||
for _ in run do end
|
||||
end, debug.traceback)
|
||||
local didNotYield, why = xpcall(function()
|
||||
for _ in run do
|
||||
end
|
||||
end, debug.traceback)
|
||||
|
||||
if didNotYield then
|
||||
if didNotYield then
|
||||
continue
|
||||
end
|
||||
|
||||
if string.find(why, "thread is not yieldable") then
|
||||
N -= 1
|
||||
local name = table.remove(systems, i)
|
||||
panic("Not allowed to yield in the systems."
|
||||
.. "\n"
|
||||
.. `System: {name} has been ejected`
|
||||
)
|
||||
panic("Not allowed to yield in the systems." .. "\n" .. `System: {name} has been ejected`)
|
||||
else
|
||||
panic(why)
|
||||
panic(why)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--debug.profileend()
|
||||
--debug.resetmemorycategory()
|
||||
return os.clock() - start
|
||||
end
|
||||
--debug.profileend()
|
||||
--debug.resetmemorycategory()
|
||||
return os.clock() - start
|
||||
end
|
||||
|
||||
return loop
|
||||
return loop
|
||||
end
|
||||
|
||||
local co = coroutine.create(btree)
|
||||
local function ai(world, dt)
|
||||
coroutine.resume(co)
|
||||
coroutine.resume(co)
|
||||
end
|
||||
|
||||
local loop = Scheduler(world, ai)
|
||||
|
||||
while wait(0.2) do
|
||||
print("frame time: ", loop(0.2))
|
||||
print("frame time: ", loop(0.2))
|
||||
end
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
local jecs = require("@jecs")
|
||||
|
||||
local function create_cache(hook)
|
||||
local columns = setmetatable({}, {
|
||||
__index = function(self, component)
|
||||
local column = {}
|
||||
self[component] = column
|
||||
return column
|
||||
end
|
||||
})
|
||||
|
||||
return function(world, component, fn)
|
||||
local column = columns[component]
|
||||
table.insert(column, fn)
|
||||
world:set(component, hook, function(entity, value)
|
||||
for _, callback in column do
|
||||
callback(entity, value)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local hooks = {
|
||||
OnSet = create_cache(jecs.OnSet),
|
||||
OnAdd = create_cache(jecs.OnAdd),
|
||||
OnRemove = create_cache(jecs.OnRemove)
|
||||
}
|
||||
|
||||
local world = jecs.World.new()
|
||||
local Position = world:component()
|
||||
local order = ""
|
||||
hooks.OnSet(world, Position, function(entity, value)
|
||||
print("$1", entity, `({value.x}, {value.y}, {value.z})`)
|
||||
order ..= "$1"
|
||||
end)
|
||||
hooks.OnSet(world, Position, function(entity, value)
|
||||
print("$2", entity, `\{{value.x}, {value.y}, {value.z}}`)
|
||||
order ..= "-$2"
|
||||
end)
|
||||
|
||||
world:set(world:entity(), Position, {x=1,y=0,z=1})
|
||||
|
||||
-- Output:
|
||||
-- $1 270 (1, 0, 1)
|
||||
-- $2 270 {1, 0, 1}
|
||||
|
||||
assert(order == "$1".."-".."$2")
|
||||
local jecs = require("@jecs")
|
||||
|
||||
local function create_cache(hook)
|
||||
local columns = setmetatable({}, {
|
||||
__index = function(self, component)
|
||||
local column = {}
|
||||
self[component] = column
|
||||
return column
|
||||
end,
|
||||
})
|
||||
|
||||
return function(world, component, fn)
|
||||
local column = columns[component]
|
||||
table.insert(column, fn)
|
||||
world:set(component, hook, function(entity, value)
|
||||
for _, callback in column do
|
||||
callback(entity, value)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local hooks = {
|
||||
OnSet = create_cache(jecs.OnSet),
|
||||
OnAdd = create_cache(jecs.OnAdd),
|
||||
OnRemove = create_cache(jecs.OnRemove),
|
||||
}
|
||||
|
||||
local world = jecs.World.new()
|
||||
local Position = world:component()
|
||||
local order = ""
|
||||
hooks.OnSet(world, Position, function(entity, value)
|
||||
print("$1", entity, `({value.x}, {value.y}, {value.z})`)
|
||||
order ..= "$1"
|
||||
end)
|
||||
hooks.OnSet(world, Position, function(entity, value)
|
||||
print("$2", entity, `\{{value.x}, {value.y}, {value.z}}`)
|
||||
order ..= "-$2"
|
||||
end)
|
||||
|
||||
world:set(world:entity(), Position, { x = 1, y = 0, z = 1 })
|
||||
|
||||
-- Output:
|
||||
-- $1 270 (1, 0, 1)
|
||||
-- $2 270 {1, 0, 1}
|
||||
|
||||
assert(order == "$1" .. "-" .. "$2")
|
||||
|
|
|
@ -1,56 +1,57 @@
|
|||
|
||||
local function calculateAverage(times)
|
||||
local sum = 0
|
||||
for _, time in ipairs(times) do
|
||||
sum = sum + time
|
||||
end
|
||||
return sum / #times
|
||||
local sum = 0
|
||||
for _, time in ipairs(times) do
|
||||
sum = sum + time
|
||||
end
|
||||
return sum / #times
|
||||
end
|
||||
|
||||
-- Main logic to time the test function
|
||||
|
||||
local CASES = {
|
||||
jecs = function(world, ...)
|
||||
for i = 1, 100 do
|
||||
local q = world:query(...)
|
||||
for _ in q do end
|
||||
end
|
||||
end,
|
||||
mirror = function(world, ...)
|
||||
for i = 1, 100 do
|
||||
local q = world:query(...)
|
||||
for _ in q do end
|
||||
end
|
||||
end
|
||||
jecs = function(world, ...)
|
||||
for i = 1, 100 do
|
||||
local q = world:query(...)
|
||||
for _ in q do
|
||||
end
|
||||
end
|
||||
end,
|
||||
mirror = function(world, ...)
|
||||
for i = 1, 100 do
|
||||
local q = world:query(...)
|
||||
for _ in q do
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
for name, fn in CASES do
|
||||
local times = {}
|
||||
local allocations = {}
|
||||
local ecs = require("@"..name)
|
||||
local world = ecs.World.new()
|
||||
local A, B, C = world:component(), world:component(), world:component()
|
||||
local times = {}
|
||||
local allocations = {}
|
||||
local ecs = require("@" .. name)
|
||||
local world = ecs.World.new()
|
||||
local A, B, C = world:component(), world:component(), world:component()
|
||||
|
||||
for i = 1, 5 do
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
world:add(e, B)
|
||||
world:add(e, C)
|
||||
end
|
||||
for i = 1, 5 do
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
world:add(e, B)
|
||||
world:add(e, C)
|
||||
end
|
||||
|
||||
collectgarbage("collect")
|
||||
local count = collectgarbage("count")
|
||||
collectgarbage("collect")
|
||||
local count = collectgarbage("count")
|
||||
|
||||
for i = 1, 50000 do
|
||||
local startTime = os.clock()
|
||||
fn(world, A, B, C)
|
||||
local allocated = collectgarbage("count")
|
||||
collectgarbage("collect")
|
||||
local endTime = os.clock()
|
||||
table.insert(times, endTime - startTime)
|
||||
table.insert(allocations, allocated)
|
||||
end
|
||||
for i = 1, 50000 do
|
||||
local startTime = os.clock()
|
||||
fn(world, A, B, C)
|
||||
local allocated = collectgarbage("count")
|
||||
collectgarbage("collect")
|
||||
local endTime = os.clock()
|
||||
table.insert(times, endTime - startTime)
|
||||
table.insert(allocations, allocated)
|
||||
end
|
||||
|
||||
print(name, "gc cycle time", calculateAverage(times))
|
||||
print(name, "memory allocated", calculateAverage(allocations))
|
||||
print(name, "gc cycle time", calculateAverage(times))
|
||||
print(name, "memory allocated", calculateAverage(allocations))
|
||||
end
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
local testkit = require("@testkit")
|
||||
local jecs = require("@jecs")
|
||||
|
||||
local world = jecs.World.new()
|
||||
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
world:add(e, B)
|
||||
local archetype_id = world.archetypeIndex["1_2"].id
|
||||
world:delete(e)
|
||||
testkit.print(world)
|
||||
local jecs = require("@jecs")
|
||||
local testkit = require("@testkit")
|
||||
|
||||
local world = jecs.World.new()
|
||||
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
world:add(e, B)
|
||||
local archetype_id = world.archetypeIndex["1_2"].id
|
||||
world:delete(e)
|
||||
testkit.print(world)
|
||||
|
|
2365
test/tests.luau
2365
test/tests.luau
File diff suppressed because it is too large
Load diff
102
testkit.luau
102
testkit.luau
|
@ -41,7 +41,7 @@ local color = {
|
|||
end,
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ local function convert_units(unit: string, value: number): (number, string)
|
|||
return value * sign, prefix_colors[order](prefixes[order] .. unit)
|
||||
end
|
||||
|
||||
local WALL = color.gray "│"
|
||||
local WALL = color.gray("│")
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Testing
|
||||
|
@ -118,7 +118,7 @@ type Case = {
|
|||
name: string,
|
||||
result: number,
|
||||
line: number?,
|
||||
focus: boolean
|
||||
focus: boolean,
|
||||
}
|
||||
|
||||
local PASS, FAIL, NONE, ERROR, SKIPPED = 1, 2, 3, 4, 5
|
||||
|
@ -129,48 +129,51 @@ local test: Test?
|
|||
local tests: { Test } = {}
|
||||
|
||||
local function output_test_result(test: Test)
|
||||
|
||||
if check_for_focused then
|
||||
local any_focused = test.focus
|
||||
for _, case in test.cases do
|
||||
any_focused = any_focused or case.focus
|
||||
end
|
||||
|
||||
if not any_focused then return end
|
||||
if not any_focused then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
print(color.white(test.name))
|
||||
|
||||
for _, case in test.cases do
|
||||
local status = ({
|
||||
[PASS] = color.green "PASS",
|
||||
[FAIL] = color.red "FAIL",
|
||||
[NONE] = color.orange "NONE",
|
||||
[ERROR] = color.red "FAIL",
|
||||
[SKIPPED] = color.yellow "SKIP"
|
||||
[PASS] = color.green("PASS"),
|
||||
[FAIL] = color.red("FAIL"),
|
||||
[NONE] = color.orange("NONE"),
|
||||
[ERROR] = color.red("FAIL"),
|
||||
[SKIPPED] = color.yellow("SKIP"),
|
||||
})[case.result]
|
||||
|
||||
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)}`)
|
||||
end
|
||||
|
||||
if test.error then
|
||||
print(color.gray "error: " .. color.red(test.error.message))
|
||||
print(color.gray "trace: " .. color.red(test.error.trace))
|
||||
print(color.gray("error: ") .. color.red(test.error.message))
|
||||
print(color.gray("trace: ") .. color.red(test.error.trace))
|
||||
else
|
||||
print()
|
||||
end
|
||||
end
|
||||
|
||||
local function CASE(name: string)
|
||||
skip = false
|
||||
skip = false
|
||||
assert(test, "no active test")
|
||||
|
||||
local case = {
|
||||
name = name,
|
||||
result = NONE,
|
||||
focus = false
|
||||
focus = false,
|
||||
}
|
||||
|
||||
test.case = case
|
||||
|
@ -183,7 +186,7 @@ local function CHECK<T>(value: T, stack: number?): T?
|
|||
local case = test.case
|
||||
|
||||
if not case then
|
||||
CASE ""
|
||||
CASE("")
|
||||
case = test.case
|
||||
end
|
||||
|
||||
|
@ -192,7 +195,7 @@ local function CHECK<T>(value: T, stack: number?): T?
|
|||
if case.result ~= FAIL then
|
||||
case.result = value and PASS or FAIL
|
||||
if skip then
|
||||
case.result = SKIPPED
|
||||
case.result = SKIPPED
|
||||
end
|
||||
case.line = debug.info(stack and stack + 1 or 2, "l")
|
||||
end
|
||||
|
@ -208,7 +211,7 @@ local function TEST(name: string, fn: () -> ())
|
|||
name = name,
|
||||
cases = {},
|
||||
duration = 0,
|
||||
focus = false
|
||||
focus = false,
|
||||
}
|
||||
assert(test)
|
||||
|
||||
|
@ -221,7 +224,9 @@ local function TEST(name: string, fn: () -> ())
|
|||
end)
|
||||
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")
|
||||
|
||||
if not success then
|
||||
|
@ -271,27 +276,14 @@ local function FINISH(): boolean
|
|||
output_test_result(test)
|
||||
end
|
||||
|
||||
print(
|
||||
color.gray(
|
||||
string.format(
|
||||
`{passed_cases}/{total_cases} test cases passed in %.3f ms.`,
|
||||
duration * 1e3
|
||||
)
|
||||
)
|
||||
)
|
||||
print(color.gray(string.format(`{passed_cases}/{total_cases} test cases passed in %.3f ms.`, duration * 1e3)))
|
||||
if check_for_focused then
|
||||
print(
|
||||
color.gray(`{passed_focus_cases}/{total_focus_cases} focused test cases passed`)
|
||||
)
|
||||
print(color.gray(`{passed_focus_cases}/{total_focus_cases} focused test cases passed`))
|
||||
end
|
||||
|
||||
local fails = total_cases - passed_cases
|
||||
|
||||
print(
|
||||
(fails > 0 and color.red or color.green)(
|
||||
`{fails} {fails == 1 and "fail" or "fails"}`
|
||||
)
|
||||
)
|
||||
print((fails > 0 and color.red or color.green)(`{fails} {fails == 1 and "fail" or "fails"}`))
|
||||
|
||||
check_for_focused = false
|
||||
return success, table.clear(tests)
|
||||
|
@ -331,7 +323,7 @@ local function BENCH(name: string, fn: () -> ())
|
|||
|
||||
bench = {}
|
||||
assert(bench);
|
||||
(collectgarbage :: any) "collect"
|
||||
(collectgarbage :: any)("collect")
|
||||
|
||||
local mem_start = gcinfo()
|
||||
local time_start = os.clock()
|
||||
|
@ -345,7 +337,7 @@ local function BENCH(name: string, fn: () -> ())
|
|||
local mem_stop = gcinfo()
|
||||
|
||||
if not success then
|
||||
print(`{WALL}{color.red "ERROR"}{WALL} {name}`)
|
||||
print(`{WALL}{color.red("ERROR")}{WALL} {name}`)
|
||||
print(color.gray(err_msg :: string))
|
||||
else
|
||||
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 d, d_unit = convert_units("s", (time_stop - time_start) / n)
|
||||
local a, a_unit =
|
||||
convert_units("B", math.round((mem_stop - mem_start) / n * 1e3))
|
||||
local a, a_unit = convert_units("B", math.round((mem_stop - mem_start) / n * 1e3))
|
||||
|
||||
local function round(x: number): string
|
||||
return x > 0
|
||||
and x < 10
|
||||
and (x - math.floor(x)) > 0
|
||||
and string.format("%2.1f", x)
|
||||
return x > 0 and x < 10 and (x - math.floor(x)) > 0 and string.format("%2.1f", x)
|
||||
or string.format("%3.f", x)
|
||||
end
|
||||
|
||||
|
@ -394,9 +382,9 @@ local function print2(v: unknown)
|
|||
|
||||
if type(value) == "string" then
|
||||
local n = str.n
|
||||
str[n + 1] = '"'
|
||||
str[n + 1] = "\""
|
||||
str[n + 2] = value
|
||||
str[n + 3] = '"'
|
||||
str[n + 3] = "\""
|
||||
str.n = n + 3
|
||||
elseif type(value) ~= "table" then
|
||||
local n = str.n
|
||||
|
@ -468,25 +456,35 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
if b[i] ~= v then return false end
|
||||
if b[i] ~= v then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for i, v in next, b do
|
||||
if a[i] ~= v then return false end
|
||||
if a[i] ~= v then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
return false
|
||||
end
|
||||
|
@ -494,7 +492,9 @@ local function deep_eq(a: {}, b: {}): boolean
|
|||
|
||||
for i, v in next, b do
|
||||
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
|
||||
return false
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue