diff --git a/bench.project.json b/bench.project.json new file mode 100644 index 00000000..e55b3eca --- /dev/null +++ b/bench.project.json @@ -0,0 +1,31 @@ +{ + "name": "jecs-test", + "tree": { + "$className": "DataModel", + "StarterPlayer": { + "$className": "StarterPlayer", + "StarterPlayerScripts": { + "$className": "StarterPlayerScripts", + "$path": "tests" + } + }, + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "Lib": { + "$path": "lib" + }, + "rgb": { + "$path": "rgb.lua" + }, + "benches": { + "$path": "benches" + }, + "mirror": { + "$path": "mirror" + }, + "DevPackages": { + "$path": "benches/visual/DevPackages" + } + } + } +} \ No newline at end of file diff --git a/benches/exhaustive.lua b/benches/exhaustive.lua deleted file mode 100644 index 3095c706..00000000 --- a/benches/exhaustive.lua +++ /dev/null @@ -1,372 +0,0 @@ -local testkit = require("../testkit") -local jecs = require("../lib/init") -local ecr = require("../DevPackages/_Index/centau_ecr@0.8.0/ecr/src/ecr") - - -local BENCH, START = testkit.benchmark() - -local function TITLE(title: string) - print() - print(testkit.color.white(title)) -end - -local N = 2^16-2 - -type i53 = number - -do TITLE "create" - BENCH("entity", function() - local world = jecs.World.new() - for i = 1, START(N) do - world:entity() - end - end) -end - ---- component benchmarks - ---todo: perform the same benchmarks for multiple components.? --- these kind of operations only support 1 component at a time, which is --- a shame, especially for archetypes where moving components is expensive. - -do TITLE "set" - BENCH("add 1 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - - for i = 1, N do - entities[i] = world:entity() - end - - for i = 1, START(N) do - world:set(entities[i], A, i) - end - end) - - BENCH("change 1 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - local e = world:entity() - world:set(e, A, 1) - - for i = 1, START(N) do - world:set(e, A, 2) - end - end) - -end - -do TITLE "remove" - BENCH("1 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - end - - for i = 1, START(N) do - world:remove(entities[i], A) - end - - end) -end - -do TITLE "get" - BENCH("1 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - end - - for i = 1, START(N) do - -- ? curious why the overhead is roughly 80 ns. - world:get(entities[i], A) - end - - end) - - BENCH("2 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - local B = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - world:set(id, B, true) - end - - for i = 1, START(N) do - world:get(entities[i], A, B) - end - - end) - - BENCH("3 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - local B = world:component() - local C = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - world:set(id, B, true) - world:set(id, C, true) - end - - for i = 1, START(N) do - world:get(entities[i], A, B, C) - end - - end) - - BENCH("4 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - world:set(id, B, true) - world:set(id, C, true) - world:set(id, D, true) - end - - for i = 1, START(N) do - world:get(entities[i], A, B, C, D) - end - - end) -end - -do TITLE (testkit.color.white_underline("Jecs query")) - - local function count(query: () -> ()) - local n = 0 - for _ in query do - n += 1 - end - return n - end - - local function flip() - return math.random() > 0.5 - end - - local function view_bench( - world: jecs.World, - A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53, I: i53 - ) - - BENCH("1 component", function() - START(count(world:query(A))) - for _ in world:query(A) do end - end) - - BENCH("2 component", function() - START(count(world:query(A, B))) - for _ in world:query(A, B) do end - end) - - BENCH("4 component", function() - START(count(world:query(A, B, C, D))) - for _ in world:query(A, B, C, D) do end - end) - - BENCH("8 component", function() - START(count(world:query(A, B, C, D, E, F, G, H))) - for _ in world:query(A, B, C, D, E, F, G, H) do end - end) - end - - do TITLE "random components" - - local world = jecs.World.new() - - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - local E = world:component() - local F = world:component() - local G = world:component() - local H = world:component() - local I = world:component() - - for i = 1, N do - local id = world:entity() - if flip() then world:set(id, A, true) end - if flip() then world:set(id, B, true) end - if flip() then world:set(id, C, true) end - if flip() then world:set(id, D, true) end - if flip() then world:set(id, E, true) end - if flip() then world:set(id, F, true) end - if flip() then world:set(id, G, true) end - if flip() then world:set(id, H, true) end - if flip() then world:set(id, I, true) end - - end - - view_bench(world, A, B, C, D, E, F, G, H, I) - - end - - do TITLE "one component in common" - - local world = jecs.World.new() - - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - local E = world:component() - local F = world:component() - local G = world:component() - local H = world:component() - local I = world:component() - - for i = 1, N do - local id = world:entity() - local a = true - if flip() then world:set(id, B, true) else a = false end - if flip() then world:set(id, C, true) else a = false end - if flip() then world:set(id, D, true) else a = false end - if flip() then world:set(id, E, true) else a = false end - if flip() then world:set(id, F, true) else a = false end - if flip() then world:set(id, G, true) else a = false end - if flip() then world:set(id, H, true) else a = false end - if flip() then world:set(id, I, true) else a = false end - if a then world:set(id, A, true) end - - end - - view_bench(world, A, B, C, D, E, F, G, H, I) - - end - -end - -do TITLE (testkit.color.white_underline("ECR query")) - - local A = ecr.component() - local B = ecr.component() - local C = ecr.component() - local D = ecr.component() - local E = ecr.component() - local F = ecr.component() - local G = ecr.component() - local H = ecr.component() - local I = ecr.component() - - local function count(query: () -> ()) - local n = 0 - for _ in query do - n += 1 - end - return n - end - - local function flip() - return math.random() > 0.5 - end - - local function view_bench( - world: ecr.Registry, - A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53, I: i53 - ) - - BENCH("1 component", function() - START(count(world:view(A))) - for _ in world:view(A) do end - end) - - BENCH("2 component", function() - START(count(world:view(A, B))) - for _ in world:view(A, B) do end - end) - - BENCH("4 component", function() - START(count(world:view(A, B, C, D))) - for _ in world:view(A, B, C, D) do end - end) - - BENCH("8 component", function() - START(count(world:view(A, B, C, D, E, F, G, H))) - for _ in world:view(A, B, C, D, E, F, G, H) do end - end) - end - - - do TITLE "random components" - local world = ecr.registry() - - for i = 1, N do - local id = world.create() - if flip() then world:set(id, A, true) end - if flip() then world:set(id, B, true) end - if flip() then world:set(id, C, true) end - if flip() then world:set(id, D, true) end - if flip() then world:set(id, E, true) end - if flip() then world:set(id, F, true) end - if flip() then world:set(id, G, true) end - if flip() then world:set(id, H, true) end - if flip() then world:set(id, I, true) end - - end - - view_bench(world, A, B, C, D, E, F, G, H, I) - - end - - do TITLE "one component in common" - - local world = ecr.registry() - - for i = 1, N do - local id = world.create() - local a = true - if flip() then world:set(id, B, true) else a = false end - if flip() then world:set(id, C, true) else a = false end - if flip() then world:set(id, D, true) else a = false end - if flip() then world:set(id, E, true) else a = false end - if flip() then world:set(id, F, true) else a = false end - if flip() then world:set(id, G, true) else a = false end - if flip() then world:set(id, H, true) else a = false end - if flip() then world:set(id, I, true) else a = false end - if a then world:set(id, A, true) end - - end - - view_bench(world, A, B, C, D, E, F, G, H, I) - - end - -end \ No newline at end of file diff --git a/benches/visual/insertion.bench.lua b/benches/visual/insertion.bench.lua index e8e50beb..8e24f299 100644 --- a/benches/visual/insertion.bench.lua +++ b/benches/visual/insertion.bench.lua @@ -2,7 +2,6 @@ --!native local ReplicatedStorage = game:GetService("ReplicatedStorage") -local rgb = require(ReplicatedStorage.rgb) local Matter = require(ReplicatedStorage.DevPackages.Matter) local jecs = require(ReplicatedStorage.Lib) local ecr = require(ReplicatedStorage.DevPackages.ecr) diff --git a/benches/visual/wally.toml b/benches/visual/wally.toml new file mode 100644 index 00000000..cb0f731b --- /dev/null +++ b/benches/visual/wally.toml @@ -0,0 +1,11 @@ +[package] +name = "private/private" +version = "0.1.0-rc.6" +registry = "https://github.com/UpliftGames/wally-index" +realm = "shared" +include = ["default.project.json", "lib/**", "lib", "wally.toml", "README.md"] +exclude = ["**"] + +[dev-dependencies] +Matter = "matter-ecs/matter@0.8.0" +ecr = "centau/ecr@0.8.0" \ No newline at end of file diff --git a/lib/init.spec.lua b/lib/init.spec.lua deleted file mode 100644 index 8de8de90..00000000 --- a/lib/init.spec.lua +++ /dev/null @@ -1,382 +0,0 @@ -local jecs = require(script.Parent) -local world = jecs.World.new() - -local A, B, C, D = world:entity(), world:entity(), world:entity(), world:entity() -local E, F, G, H = world:entity(), world:entity(), world:entity(), world:entity() -print("A", A) -print("B", B) -print("C", C) -print("D", D) -print("E", E) -print("F", F) -print("G", G) -print("H", H) - -local common = 0 -local N = 2^16-2 -local archetypes = {} -local function flip() - return math.random() >= 0.5 -end - -local amountOfCombination = 0 -for i = 1, N do - local entity = world:entity() - local combination = "" - - if flip() then - combination ..= "2_" - world:set(entity, B, { value = true}) - end - if flip() then - combination ..= "3_" - world:set(entity, C, { value = true}) - end - if flip() then - combination ..= "4_" - world:set(entity, D, { value = true}) - end - if flip() then - combination ..= "5_" - world:set(entity, E, { value = true}) - end - if flip() then - combination ..= "6_" - world:set(entity, F, { value = true}) - end - if flip() then - combination ..= "7_" - world:set(entity, G, { value = true}) - end - if flip() then - combination ..= "8" - world:set(entity, H, { value = true}) - end - - if #combination == 7 then - combination = "1_" .. combination - common += 1 - world:set(entity, A, { value = true}) - end - - if combination:find("2") - and combination:find("3") - and combination:find("4") - and combination:find("6") - then - amountOfCombination += 1 - end - archetypes[combination] = true -end - -return function() - describe("World", function() - it("should add component", function() - local id = world:entity() - world:set(id, A, true) - world:set(id, B, 1) - - local id1 = world:entity() - world:set(id1, A, "hello") - expect(world:get(id, A)).to.equal(true) - expect(world:get(id, B)).to.equal(1) - expect(world:get(id1, A)).to.equal("hello") - end) - - it("should remove component", function() - local Tag = world:entity() - local entities = {} - for i = 1, 10 do - local entity = world:entity() - entities[i] = entity - world:set(entity, Tag) - end - - for i = 1, 10 do - local entity = entities[i] - expect(world:get(entity, Tag)).to.equal(nil) - world:remove(entity, Tag) - end - - end) - - it("should override component data", function() - - local id = world:entity() - world:set(id, A, true) - expect(world:get(id, A)).to.equal(true) - - world:set(id, A, false) - expect(world:get(id, A)).to.equal(false) - - end) - - it("should not query a removed component", function() - local Tag = world:entity() - local AnotherTag = world:entity() - - local entity = world:entity() - world:set(entity, Tag) - world:set(entity, AnotherTag) - world:remove(entity, AnotherTag) - - local added = 0 - for e, t, a in world:query(Tag, AnotherTag) do - added += 1 - end - expect(added).to.equal(0) - end) - - it("should query correct number of compatible archetypes", function() - local added = 0 - for _ in world:query(B, C, D, F) do - added += 1 - end - expect(added).to.equal(amountOfCombination) - end) - - it("should not query poisoned players", function() - local Player = world:entity() - local Health = world:entity() - local Poison = world:entity() - - local one = world:entity() - world:set(one, Player, { name = "alice"}) - world:set(one, Health, 100) - world:set(one, Poison) - - local two = world:entity() - world:set(two, Player, { name = "bob"}) - world:set(two, Health, 90) - - local withoutCount = 0 - for _id, _player in world:query(Player):without(Poison) do - withoutCount += 1 - end - - expect(withoutCount).to.equal(1) - end) - - it("should allow calling world:entity before world:component", function() - for _ = 1, 256 do - world:entity() - end - expect(world:component()).to.be.ok() - end) - - it("should skip iteration", function() - local Position, Velocity = world:entity(), world:entity() - local e = world:entity() - world:set(e, Position, Vector3.zero) - world:set(e, Velocity, Vector3.one) - local added = 0 - for i in world:query(Position):without(Velocity) do - added += 1 - end - expect(added).to.equal(0) - end) - - it("should query all matching entities", function() - - local world = jecs.World.new() - local A = world:component() - local B = world:component() - - local entities = {} - for i = 1, N do - local id = world:entity() - - - world:set(id, A, true) - if i > 5 then world:set(id, B, true) end - entities[i] = id - end - - for id in world:query(A) do - local i = table.find(entities, id) - expect(i).to.be.ok() - table.remove(entities, i) - end - - expect(#entities).to.equal(0) - end) - - it("should query all matching entities when irrelevant component is removed", function() - - - local world = jecs.World.new() - local A = world:component() - local B = world:component() - - local entities = {} - for i = 1, N do - local id = world:entity() - - world:set(id, A, true) - world:set(id, B, true) - if i > 5 then world:remove(id, B, true) end - entities[i] = id - end - - local added = 0 - for id in world:query(A) do - added += 1 - local i = table.find(entities, id) - expect(i).to.be.ok() - table.remove(entities, i) - end - - expect(added).to.equal(N) - end) - - it("should query all entities without B", function() - local world = jecs.World.new() - local A = world:component() - local B = world:component() - - local entities = {} - for i = 1, N do - local id = world:entity() - - world:set(id, A, true) - if i < 5 then - entities[i] = id - else - world:set(id, B, true) - end - - end - - for id in world:query(A):without(B) do - local i = table.find(entities, id) - expect(i).to.be.ok() - table.remove(entities, i) - end - - expect(#entities).to.equal(0) - end) - - it("should allow setting components in arbitrary order", function() - local world = jecs.World.new() - - local Health = world:entity() - local Poison = world:component() - - local id = world:entity() - world:set(id, Poison, 5) - world:set(id, Health, 50) - - expect(world:get(id, Poison)).to.equal(5) - end) - - it("Should allow deleting components", function() - local world = jecs.World.new() - - local Health = world:entity() - local Poison = world:component() - - local id = world:entity() - world:set(id, Poison, 5) - world:set(id, Health, 50) - world:delete(id) - - expect(world:get(id, Poison)).to.never.be.ok() - expect(world:get(id, Health)).to.never.be.ok() - end) - - it("should allow iterating the whole world", function() - local world = jecs.World.new() - - local A, B = world:entity(), world:entity() - - local eA = world:entity() - world:set(eA, A, true) - local eB = world:entity() - world:set(eB, B, true) - local eAB = world:entity() - world:set(eAB, A, true) - world:set(eAB, B, true) - - local count = 0 - for id, data in world do - count += 1 - if id == eA then - expect(data[A]).to.be.ok() - expect(data[B]).to.never.be.ok() - elseif id == eB then - expect(data[B]).to.be.ok() - expect(data[A]).to.never.be.ok() - elseif id == eAB then - expect(data[A]).to.be.ok() - expect(data[B]).to.be.ok() - end - end - - expect(count).to.equal(5) - end) - - it("should allow querying for relations", function() - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local bob = world:entity() - - world:set(bob, jecs.pair(Eats, Apples), true) - for e, bool in world:query(jecs.pair(Eats, Apples)) do - expect(e).to.equal(bob) - expect(bool).to.equal(bool) - end - end) - - it("should allow wildcards in queries", function() - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local bob = world:entity() - - world:set(bob, jecs.pair(Eats, Apples), "bob eats apples") - for e, data in world:query(jecs.pair(Eats, jecs.w)) do - expect(e).to.equal(bob) - expect(data).to.equal("bob eats apples") - end - for e, data in world:query(jecs.pair(jecs.w, Apples)) do - expect(e).to.equal(bob) - expect(data).to.equal("bob eats apples") - end - end) - - it("should match against multiple pairs", function() - local world = jecs.World.new() - local pair = jecs.pair - local Eats = world:entity() - local Apples = world:entity() - local Oranges =world:entity() - local bob = world:entity() - local alice = world:entity() - - world:set(bob, pair(Eats, Apples), "bob eats apples") - world:set(alice, pair(Eats, Oranges), "alice eats oranges") - - local w = jecs.Wildcard - - local count = 0 - for e, data in world:query(pair(Eats, w)) do - count += 1 - if e == bob then - expect(data).to.equal("bob eats apples") - else - expect(data).to.equal("alice eats oranges") - end - end - - expect(count).to.equal(2) - count = 0 - - for e, data in world:query(pair(w, Apples)) do - count += 1 - expect(data).to.equal("bob eats apples") - end - expect(count).to.equal(1) - end) - end) -end \ No newline at end of file diff --git a/test.project.json b/test.project.json index bdcbd0b5..0a3901a2 100644 --- a/test.project.json +++ b/test.project.json @@ -22,18 +22,6 @@ }, "mirror": { "$path": "mirror" - }, - "DevPackages": { - "$path": "DevPackages" - } - }, - "TestService": { - "$properties": { - "ExecuteWithStudioRun": true - }, - "$className": "TestService", - "run": { - "$path": "tests.server.lua" } } } diff --git a/tests.server.lua b/tests.server.lua deleted file mode 100644 index 683913d8..00000000 --- a/tests.server.lua +++ /dev/null @@ -1,9 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") - -require(ReplicatedStorage.DevPackages.TestEZ).TestBootstrap:run({ - ReplicatedStorage.Lib, - nil, - { - noXpcallByDefault = true, - }, -}) diff --git a/wally.toml b/wally.toml index 879b3e75..a19b86fa 100644 --- a/wally.toml +++ b/wally.toml @@ -4,7 +4,4 @@ version = "0.1.0" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" include = ["default.project.json", "lib/**", "lib", "wally.toml", "README.md"] -exclude = ["**"] - -[dev-dependencies] -TestEZ = "roblox/testez@0.4.1" \ No newline at end of file +exclude = ["**"] \ No newline at end of file