commit 6cafbc1221adb55aab3e56615c0e29d823f2a490 Author: Ukendio Date: Tue Apr 23 17:10:49 2024 +0200 Rebase diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd73bca --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Compiled Lua sources +luac.out + +# luarocks build files +*.src.rock +*.zip +*.tar.gz +*.rbxm + +# Object files +*.o +*.os +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo +*.def +*.exp + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Wally files +DevPackages +Packages +wally.lock +WallyPatches + +# Misc +roblox.toml +sourcemap.json +drafts diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed40147 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +Just an ECS + +jecs provides \ No newline at end of file diff --git a/aftman.toml b/aftman.toml new file mode 100644 index 0000000..73e1123 --- /dev/null +++ b/aftman.toml @@ -0,0 +1,6 @@ +[tools] +wally = "upliftgames/wally@0.3.1" +rojo = "rojo-rbx/rojo@7.4.1" +stylua = "johnnymorganz/stylua@0.19.1" +selene = "kampfkarren/selene@0.26.1" +wally-patch-package="Barocena/wally-patch-package@1.2.1" \ No newline at end of file diff --git a/benches/insertion.bench.lua b/benches/insertion.bench.lua new file mode 100644 index 0000000..abba6c7 --- /dev/null +++ b/benches/insertion.bench.lua @@ -0,0 +1,96 @@ +--!optimize 2 +--!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) +local newWorld = Matter.World.new() +local ecs = jecs.World.new() + +local A1 = Matter.component() +local A2 = Matter.component() +local A3 = Matter.component() +local A4 = Matter.component() +local A5 = Matter.component() +local A6 = Matter.component() +local A7 = Matter.component() +local A8 = Matter.component() + +local B1 = ecr.component() +local B2 = ecr.component() +local B3 = ecr.component() +local B4 = ecr.component() +local B5 = ecr.component() +local B6 = ecr.component() +local B7 = ecr.component() +local B8 = ecr.component() + +local C1 = ecs:entity() +local C2 = ecs:entity() +local C3 = ecs:entity() +local C4 = ecs:entity() +local C5 = ecs:entity() +local C6 = ecs:entity() +local C7 = ecs:entity() +local C8 = ecs:entity() + +local registry2 = ecr.registry() +return { + ParameterGenerator = function() + return + end, + + Functions = { + Matter = function() + for i = 1, 50 do + newWorld:spawn( + A1({ value = true }), + A2({ value = true }), + A3({ value = true }), + A4({ value = true }), + A5({ value = true }), + A6({ value = true }), + A7({ value = true }), + A8({ value = true }) + ) + end + end, + + + ECR = function() + for i = 1, 50 do + local e = registry2.create() + registry2:set(e, B1, {value = false}) + registry2:set(e, B2, {value = false}) + registry2:set(e, B3, {value = false}) + registry2:set(e, B4, {value = false}) + registry2:set(e, B5, {value = false}) + registry2:set(e, B6, {value = false}) + registry2:set(e, B7, {value = false}) + registry2:set(e, B8, {value = false}) + end + end, + + + Jecs = function() + + local e = ecs:entity() + + for i = 1, 50 do + + ecs:add(e, C1, {value = false}) + ecs:add(e, C2, {value = false}) + ecs:add(e, C3, {value = false}) + ecs:add(e, C4, {value = false}) + ecs:add(e, C5, {value = false}) + ecs:add(e, C6, {value = false}) + ecs:add(e, C7, {value = false}) + ecs:add(e, C8, {value = false}) + + end + end + + }, +} diff --git a/benches/query.bench.lua b/benches/query.bench.lua new file mode 100644 index 0000000..42eeaf0 --- /dev/null +++ b/benches/query.bench.lua @@ -0,0 +1,162 @@ +--!optimize 2 +--!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) +local newWorld = Matter.World.new() +local ecs = jecs.World.new() + +local A1 = Matter.component() +local A2 = Matter.component() +local A3 = Matter.component() +local A4 = Matter.component() +local A5 = Matter.component() +local A6 = Matter.component() +local A7 = Matter.component() +local A8 = Matter.component() + +local B1 = ecr.component() +local B2 = ecr.component() +local B3 = ecr.component() +local B4 = ecr.component() +local B5 = ecr.component() +local B6 = ecr.component() +local B7 = ecr.component() +local B8 = ecr.component() + +local C1 = ecs:entity() +local C2 = ecs:entity() +local C3 = ecs:entity() +local C4 = ecs:entity() +local C5 = ecs:entity() +local C6 = ecs:entity() +local C7 = ecs:entity() +local C8 = ecs:entity() + +local registry2 = ecr.registry() + +local function flip() + return math.random() >= 0.15 +end + +local common = 0 +local N = 2^16-2 +local archetypes = {} +for i = 1, N do + local id = registry2.create() + local combination = "" + local n = newWorld:spawn() + + local entity = ecs:entity() + + if flip() then + combination ..= "B" + registry2:set(id, B2, {value = true}) + ecs:add(entity, C2, { value = true}) + newWorld:insert(n, A2({value = true})) + end + if flip() then + combination ..= "C" + registry2:set(id, B3, {value = true}) + ecs:add(entity, C3, { value = true}) + newWorld:insert(n, A3({value = true})) + end + if flip() then + combination ..= "D" + registry2:set(id, B4, {value = true}) + ecs:add(entity, C4, { value = true}) + newWorld:insert(n, A4({value = true})) + end + if flip() then + combination ..= "E" + registry2:set(id, B5, {value = true}) + ecs:add(entity, C5, { value = true}) + newWorld:insert(n, A5({value = true})) + end + if flip() then + combination ..= "F" + registry2:set(id, B6, {value = true}) + ecs:add(entity, C6, { value = true}) + newWorld:insert(n, A6({value = true})) + end + if flip() then + combination ..= "G" + registry2:set(id, B7, {value = true}) + ecs:add(entity, C7, { value = true}) + newWorld:insert(n, A7({value = true})) + end + if flip() then + combination ..= "H" + registry2:set(id, B8, {value = true}) + ecs:add(entity, C8, { value = true}) + newWorld:insert(n, A8({value = true})) + end + + if #combination == 7 then + combination = "A" .. combination + common += 1 + registry2:set(id, B1, {value = true}) + ecs:add(entity, C1, { value = true}) + newWorld:insert(n, A1({value = true})) + end + + archetypes[combination] = true +end + +local white = rgb.white +local yellow = rgb.yellow +local gray = rgb.gray +local green = rgb.green + +local WALL = gray(" │ ") + +local numberOfArchetypes = 0 +for _ in archetypes do + numberOfArchetypes += 1 +end +print(common) + +print( + "N entities "..yellow(N) + ..WALL + .."with common components: " + ..yellow(tostring(common).."/"..tostring(N)).." " + ..yellow("("..string.format("%.2f", (common / (2^16 - 2)* 100)).."%)") + ..WALL + ..yellow("Total Archetypes: "..numberOfArchetypes) +) + +return { + ParameterGenerator = function() + return + end, + + Functions = { + Matter = function() + local matched = 0 + for entityId, firstComponent in newWorld:query(A1, A2, A3, A4) do + matched += 1 + end + end, + + + ECR = function() + local matched = 0 + for entityId, firstComponent in registry2:view(B1, B2, B3, B4) do + matched += 1 + end + end, + + + Jecs = function() + local matched = 0 + for entityId, firstComponent in ecs:query(C1, C2, C3, C4) do + matched += 1 + end + end + + }, +} diff --git a/benches/spawn.bench.lua b/benches/spawn.bench.lua new file mode 100644 index 0000000..962064e --- /dev/null +++ b/benches/spawn.bench.lua @@ -0,0 +1,42 @@ +--!optimize 2 +--!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) +local newWorld = Matter.World.new() +local ecs = jecs.World.new() + + +return { + ParameterGenerator = function() + local registry2 = ecr.registry() + + return registry2 + end, + + Functions = { + Matter = function() + for i = 1, 1000 do + newWorld:spawn() + end + end, + + + ECR = function(_, registry2) + for i = 1, 1000 do + registry2.create() + end + end, + + + Jecs = function() + for i = 1, 1000 do + ecs:entity() + end + end + + }, +} diff --git a/default.project.json b/default.project.json new file mode 100644 index 0000000..03c9e72 --- /dev/null +++ b/default.project.json @@ -0,0 +1,6 @@ +{ + "name": "jecs", + "tree": { + "$path": "lib" + } + } \ No newline at end of file diff --git a/lib/init.lua b/lib/init.lua new file mode 100644 index 0000000..def3b2f --- /dev/null +++ b/lib/init.lua @@ -0,0 +1,405 @@ +--!optimize 2 +--!native +--!strict +--draft 4 + +type i53 = number +type i24 = number + +type Ty = { i53 } +type ArchetypeId = number + +type Column = { any } + +type Archetype = { + id: number, + edges: { + [i24]: { + add: Archetype, + remove: Archetype, + }, + }, + types: Ty, + type: string | number, + entities: { number }, + columns: { Column }, + records: {}, +} + +type Record = { + archetype: Archetype, + row: number, +} + +type EntityIndex = { [i24]: Record } +type ComponentIndex = { [i24]: ArchetypeMap} + +type ArchetypeRecord = number +type ArchetypeMap = { [ArchetypeId]: ArchetypeRecord } +type Archetypes = { [ArchetypeId]: Archetype } + +local function transitionArchetype( + entityIndex: EntityIndex, + destinationArchetype: Archetype, + destinationRow: i24, + sourceArchetype: Archetype, + sourceRow: i24 +) + local columns = sourceArchetype.columns + local sourceEntities = sourceArchetype.entities + local destinationEntities = destinationArchetype.entities + local destinationColumns = destinationArchetype.columns + + for componentId, column in columns do + local targetColumn = destinationColumns[componentId] + if targetColumn then + targetColumn[destinationRow] = column[sourceRow] + end + column[sourceRow] = column[#column] + column[#column] = nil + end + + destinationEntities[destinationRow] = sourceEntities[sourceRow] + local moveAway = #sourceEntities + sourceEntities[sourceRow] = sourceEntities[moveAway] + sourceEntities[moveAway] = nil + entityIndex[destinationEntities[destinationRow]].row = sourceRow +end + +local function archetypeAppend(entity: i53, archetype: Archetype): i24 + local entities = archetype.entities + table.insert(entities, entity) + return #entities +end + +local function newEntity(entityId: i53, record: Record, archetype: Archetype) + local row = archetypeAppend(entityId, archetype) + record.archetype = archetype + record.row = row + return record +end + +local function moveEntity(entityIndex, entityId: i53, record: Record, to: Archetype) + local sourceRow = record.row + local from = record.archetype + local destinationRow = archetypeAppend(entityId, to) + transitionArchetype(entityIndex, to, destinationRow, from, sourceRow) + record.archetype = to + record.row = destinationRow +end + +local function hash(arr): string | number + if true then + return table.concat(arr, "_") + end + local hashed = 5381 + for i = 1, #arr do + hashed = ((bit32.lshift(hashed, 5)) + hashed) + arr[i] + end + return hashed +end + +local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, from: Archetype?) + local destinationCount = #to.types + local destinationIds = to.types + + for i = 1, destinationCount do + local destinationId = destinationIds[i] + + if not componentIndex[destinationId] then + componentIndex[destinationId] = {} + end + componentIndex[destinationId][to.id] = i + to.records[destinationId] = i + end +end + +local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Archetype + local ty = hash(types) + + world.nextArchetypeId = (world.nextArchetypeId::number)+ 1 + local id = world.nextArchetypeId + + local columns = {} :: { any } + + for _ in types do + table.insert(columns, {}) + end + + local archetype = { + id = id, + types = types, + type = ty, + columns = columns, + entities = {}, + edges = {}, + records = {}, + } + world.archetypeIndex[ty] = archetype + world.archetypes[id] = archetype + createArchetypeRecords(world.componentIndex, archetype, prev) + + return archetype +end + +local World = {} +World.__index = World +function World.new() + local self = setmetatable({ + entityIndex = {}, + componentIndex = {}, + archetypes = {}, + archetypeIndex = {}, + ROOT_ARCHETYPE = nil :: Archetype?, + nextId = 0, + nextArchetypeId = 0 + }, World) + self.ROOT_ARCHETYPE = archetypeOf(self, {}, nil) + return self +end + +type World = typeof(World.new()) + +local function ensureArchetype(world: World, types, prev) + if #types < 1 then + + if not world.ROOT_ARCHETYPE then + local ROOT_ARCHETYPE = archetypeOf(world, {}, nil) + world.ROOT_ARCHETYPE = ROOT_ARCHETYPE + return ROOT_ARCHETYPE + end + end + local ty = hash(types) + local archetype = world.archetypeIndex[ty] + if archetype then + return archetype + end + + return archetypeOf(world, types, prev) +end + +local function findInsert(types: { i53 }, toAdd: i53) + local count = #types + for i = 1, count do + local id = types[i] + if id == toAdd then + return -1 + end + if id > toAdd then + return i + end + end + return count + 1 +end + +local function findArchetypeWith(world: World, node: Archetype, componentId: i53) + local types = node.types + local at = findInsert(types, componentId) + if at == -1 then + return node + end + + local destinationType = table.clone(node.types) + table.insert(destinationType, at, componentId) + return ensureArchetype(world, destinationType, node) +end + +local function ensureEdge(archetype: Archetype, componentId: i53) + if not archetype.edges[componentId] then + archetype.edges[componentId] = {} :: any + end + return archetype.edges[componentId] +end + +local function archetypeTraverseAdd(world: World, componentId: i53, archetype: Archetype?): Archetype + local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype + local edge = ensureEdge(from, componentId) + + if not edge.add then + edge.add = findArchetypeWith(world, from, componentId) + end + + return edge.add +end + +function World.ensureRecord(world: World, entityId: i53) + local entityIndex = world.entityIndex + local id = entityId + if not entityIndex[id] then + entityIndex[id] = {} :: Record + end + return entityIndex[id] +end + +function World.add(world: World, entityId: i53, componentId: i53, data: unknown) + local record = world:ensureRecord(entityId) + local sourceArchetype = record.archetype + local destinationArchetype = archetypeTraverseAdd(world, componentId, sourceArchetype) + + if sourceArchetype and not (sourceArchetype == destinationArchetype) then + moveEntity(world.entityIndex, entityId, record, destinationArchetype) + else + -- if it has any components, then it wont be the root archetype + if #destinationArchetype.types > 0 then + newEntity(entityId, record, destinationArchetype) + end + end + + local archetypeRecord = destinationArchetype.records[componentId] + destinationArchetype.columns[archetypeRecord][record.row] = data +end + +local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype + local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype + local edge = ensureEdge(from, componentId) + + + if not edge.remove then + local to = table.clone(from.types) + table.remove(to, table.find(to, componentId)) + edge.remove = ensureArchetype(world, to, from) + end + + return edge.remove +end + +function World.remove(world: World, entityId: i53, componentId: i53) + local record = world:ensureRecord(entityId) + local sourceArchetype = record.archetype + local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype) + + if sourceArchetype and not (sourceArchetype == destinationArchetype) then + moveEntity(world.entityIndex, entityId, record, destinationArchetype) + end +end + +local function get(componentIndex: { [i24]: ArchetypeMap }, record: Record, componentId: i24) + local archetype = record.archetype + local archetypeRecord = componentIndex[componentId][archetype.id] + + if not archetypeRecord then + return nil + end + + return archetype.columns[archetypeRecord][record.row] +end + +function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?) + local id = entityId + local componentIndex = world.componentIndex + local record = world.entityIndex[id] + if not record then + return nil + end + + local va = get(componentIndex, record, a) + + if b == nil then + return va + elseif c == nil then + return va, get(componentIndex, record, b) + elseif d == nil then + return va, get(componentIndex, record, b), get(componentIndex, record, c) + elseif e == nil then + return va, get(componentIndex, record, b), get(componentIndex, record, c), get(componentIndex, record, d) + else + error("args exceeded") + end +end + +function World.entity(world: World) + world.nextId += 1 + return world.nextId +end + +function World.archetypesWith(world: World, componentId: i53) + local archetypes = world.archetypes + local archetypeMap = world.componentIndex[componentId] + local compatibleArchetypes = {} + for id, archetypeRecord in archetypeMap do + compatibleArchetypes[archetypes[id]] = true + end + return compatibleArchetypes +end + + + +function World.query(world: World, ...: i53): () -> (number, ...any) + local compatibleArchetypes = {} + local components = { ... } + local archetypes = world.archetypes + local queryLength = #components + local a, b, c, d, e = ... + local firstArchetypeMap = world.componentIndex[components[1]] + + for id in firstArchetypeMap do + local archetype = archetypes[id] + local archetypeRecords = archetype.records + local matched = true + for _, componentId in components do + if not archetypeRecords[componentId] then + matched = false + break + end + end + if matched then + table.insert(compatibleArchetypes, archetype) + end + end + local lastArchetype, archetype = next(compatibleArchetypes) + + local lastRow + + local function queryNext(): (...any) + local row = next(archetype.entities, lastRow) + while row == nil do + lastArchetype, archetype = next(compatibleArchetypes, lastArchetype) + if lastArchetype == nil then + return + end + row = next(archetype.entities, row) + end + lastRow = row + + local columns = archetype.columns + local entityId = archetype.entities[row :: number] + local archetypeRecords = archetype.records + + if queryLength == 1 then + return entityId, columns[archetypeRecords[a]] + elseif queryLength == 2 then + return entityId, columns[archetypeRecords[a]] + elseif queryLength == 3 then + return entityId, columns[archetypeRecords[a]] + elseif queryLength == 4 then + return entityId, + columns[archetypeRecords[a]], + columns[archetypeRecords[b]], + columns[archetypeRecords[c]], + columns[archetypeRecords[d]] + elseif queryLength == 5 then + return entityId, + columns[archetypeRecords[a]], + columns[archetypeRecords[b]], + columns[archetypeRecords[c]], + columns[archetypeRecords[d]], + columns[archetypeRecords[e]] + end + + local queryOutput = {} + for i, componentId in components do + queryOutput[i] = columns[archetypeRecords[componentId]] + end + + return entityId, unpack(queryOutput, 1, queryLength) + end + + return function() + -- consider this to be the iterator that gets invoked each iteration step + return queryNext() + end +end + +return { + World = World +} \ No newline at end of file diff --git a/lib/init.spec.lua b/lib/init.spec.lua new file mode 100644 index 0000000..6096865 --- /dev/null +++ b/lib/init.spec.lua @@ -0,0 +1,70 @@ +local ecs = require(script.Parent).World.new() + +local A, B, C, D = ecs:entity(), ecs:entity(), ecs:entity(), ecs:entity() +local E, F, G, H = ecs:entity(), ecs:entity(), ecs:entity(), ecs: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) + +for i = 1, 256 do + local entity = ecs:entity() + ecs:add(entity, A, true) + ecs:add(entity, B, true) + ecs:add(entity, C, true) + ecs:add(entity, D, true) + + --[[ + ecs:add(entity, E, true) + ecs:add(entity, F, true) + ecs:add(entity, G, true) + ecs:add(entity, H, true) + print("end") + ]] +end + +return function() + describe("World", function() + it("should add component", function() + local id = ecs:entity() + ecs:add(id, A, true) + ecs:add(id, B, 1) + + local id1 = ecs:entity() + ecs:add(id1, A, "hello") + expect(ecs:get(id, A)).to.equal(true) + expect(ecs:get(id, B)).to.equal(1) + expect(ecs:get(id1, A)).to.equal("hello") + end) + it("should remove component", function() + local id = ecs:entity() + ecs:add(id, A, true) + ecs:add(id, B, 1000) + ecs:remove(id, A, false) + + expect(ecs:get(id, A)).to.equal(nil) + end) + it("should override component data", function() + + local id = ecs:entity() + ecs:add(id, A, true) + expect(ecs:get(id, A)).to.equal(true) + + ecs:add(id, A, false) + expect(ecs:get(id, A)).to.equal(false) + + end) + it("query", function() + local added = 0 + for e, a, b, c, d in ecs:query(A, B, C, D) do + added += 1 + end + expect(added).to.equal(256) + end) + + end) +end \ No newline at end of file diff --git a/rgb.lua b/rgb.lua new file mode 100644 index 0000000..11880c7 --- /dev/null +++ b/rgb.lua @@ -0,0 +1,33 @@ +return { + white_underline = function(s: any) + return `\27[1;4m{s}\27[0m` + end, + + white = function(s: any) + return `\27[37;1m{s}\27[0m` + end, + + green = function(s: any) + return `\27[32;1m{s}\27[0m` + end, + + red = function(s: any) + return `\27[31;1m{s}\27[0m` + end, + + yellow = function(s: any) + return `\27[33;1m{s}\27[0m` + end, + + red_highlight = function(s: any) + return `\27[41;1;30m{s}\27[0m` + end, + + green_highlight = function(s: any) + return `\27[42;1;30m{s}\27[0m` + end, + + gray = function(s: any) + return `\27[30;1m{s}\27[0m` + end, +} \ No newline at end of file diff --git a/test.project.json b/test.project.json new file mode 100644 index 0000000..ab104eb --- /dev/null +++ b/test.project.json @@ -0,0 +1,38 @@ +{ + "name": "jecs-test", + "tree": { + "$className": "DataModel", + "StarterPlayer": { + "$className": "StarterPlayer", + "StarterPlayerScripts": { + "$className": "StarterPlayerScripts", + "$path": "tests" + } + }, + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "DevPackages": { + "$path": "DevPackages" + }, + "Lib": { + "$path": "lib" + }, + "rgb": { + "$path": "rgb.lua" + }, + "benches": { + "$path": "benches" + } + }, + "TestService": { + "$properties": { + "ExecuteWithStudioRun": true + }, + "$className": "TestService", + "run": { + "$path": "tests.server.lua" + } + } + } + } + \ No newline at end of file diff --git a/testez.d.lua b/testez.d.lua new file mode 100644 index 0000000..349ea08 --- /dev/null +++ b/testez.d.lua @@ -0,0 +1,24 @@ +declare function afterAll(callback: () -> ()): () +declare function afterEach(callback: () -> ()): () + +declare function beforeAll(callback: () -> ()): () +declare function beforeEach(callback: () -> ()): () + +declare function describe(phrase: string, callback: () -> ()): () +declare function describeFOCUS(phrase: string, callback: () -> ()): () +declare function fdescribe(phrase: string, callback: () -> ()): () +declare function describeSKIP(phrase: string, callback: () -> ()): () +declare function xdescribe(phrase: string, callback: () -> ()): () + +declare function expect(value: any): any + +declare function FIXME(optionalMessage: string?): () +declare function FOCUS(): () +declare function SKIP(): () + +declare function it(phrase: string, callback: () -> ()): () +declare function itFOCUS(phrase: string, callback: () -> ()): () +declare function fit(phrase: string, callback: () -> ()): () +declare function itSKIP(phrase: string, callback: () -> ()): () +declare function xit(phrase: string, callback: () -> ()): () +declare function itFIXME(phrase: string, callback: () -> ()): () \ No newline at end of file diff --git a/tests.server.lua b/tests.server.lua new file mode 100644 index 0000000..683913d --- /dev/null +++ b/tests.server.lua @@ -0,0 +1,9 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +require(ReplicatedStorage.DevPackages.TestEZ).TestBootstrap:run({ + ReplicatedStorage.Lib, + nil, + { + noXpcallByDefault = true, + }, +}) diff --git a/thesis/images/archetype_graph.png b/thesis/images/archetype_graph.png new file mode 100644 index 0000000..140def3 Binary files /dev/null and b/thesis/images/archetype_graph.png differ diff --git a/thesis/images/chrome_IdcpbCveiD.png b/thesis/images/chrome_IdcpbCveiD.png new file mode 100644 index 0000000..0bf4848 Binary files /dev/null and b/thesis/images/chrome_IdcpbCveiD.png differ diff --git a/thesis/images/chrome_f5DTavXIka.png b/thesis/images/chrome_f5DTavXIka.png new file mode 100644 index 0000000..935489f Binary files /dev/null and b/thesis/images/chrome_f5DTavXIka.png differ diff --git a/thesis/images/chrome_giChmd5W4Z.png b/thesis/images/chrome_giChmd5W4Z.png new file mode 100644 index 0000000..254871b Binary files /dev/null and b/thesis/images/chrome_giChmd5W4Z.png differ diff --git a/thesis/images/insertion.png b/thesis/images/insertion.png new file mode 100644 index 0000000..f6facf6 Binary files /dev/null and b/thesis/images/insertion.png differ diff --git a/thesis/images/queries.png b/thesis/images/queries.png new file mode 100644 index 0000000..a511784 Binary files /dev/null and b/thesis/images/queries.png differ diff --git a/thesis/images/random_access.png b/thesis/images/random_access.png new file mode 100644 index 0000000..6b122af Binary files /dev/null and b/thesis/images/random_access.png differ diff --git a/thesis/images/removed.png b/thesis/images/removed.png new file mode 100644 index 0000000..03cc9b6 Binary files /dev/null and b/thesis/images/removed.png differ diff --git a/thesis/images/sparseset.png b/thesis/images/sparseset.png new file mode 100644 index 0000000..0cdea79 Binary files /dev/null and b/thesis/images/sparseset.png differ diff --git a/wally.toml b/wally.toml new file mode 100644 index 0000000..1038ec2 --- /dev/null +++ b/wally.toml @@ -0,0 +1,10 @@ +[package] +name = "marcus/jade" +version = "0.1.0" +registry = "https://github.com/UpliftGames/wally-index" +realm = "shared" + +[dev-dependencies] +TestEZ = "roblox/testez@0.4.1" +Matter = "matter-ecs/matter@0.7.1" +ecr = "centau/ecr@0.8.0"