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