fix: Bugs + temp push for bugfixing

This commit is contained in:
Mark Marks 2024-09-22 00:05:15 +02:00
parent 88ca58df9b
commit d34edf8d70
21 changed files with 2317 additions and 73 deletions

View file

@ -5,7 +5,7 @@
"current": { "current": {
"name": "path", "name": "path",
"sources": { "sources": {
"@pkg": "Packages/" "@jecs": "lib/jecs"
} }
}, },
"target": { "target": {

View file

@ -66,6 +66,13 @@ jobs:
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run tests - name: Run Unit Tests
run: | run: |
lune run test/runner.luau output=$(luau test/tests.luau)
echo "$output"
if [[ "$output" == *"0 fails"* ]]; then
echo "Unit Tests Passed"
else
echo "Error: One or More Unit Tests Failed."
exit 1
fi

View file

@ -2,6 +2,7 @@
"languageMode": "strict", "languageMode": "strict",
"aliases": { "aliases": {
"jecs_utils": "lib", "jecs_utils": "lib",
"testkit": "test/testkit" "testkit": "test/testkit",
"jecs": "lib/jecs"
} }
} }

View file

@ -9,9 +9,9 @@ local function start_process(cmd: string)
process.spawn(command, arguments, { stdio = "forward" }) process.spawn(command, arguments, { stdio = "forward" })
end end
start_process("lune run install-packages.luau") --start_process("lune run install-packages.luau")
--start_process("curl -O https://raw.githubusercontent.com/JohnnyMorganz/luau-lsp/main/scripts/globalTypes.d.luau") --start_process("curl -O https://raw.githubusercontent.com/JohnnyMorganz/luau-lsp/main/scripts/globalTypes.d.luau")
start_process("rojo sourcemap dev.project.json -o sourcemap.json") start_process("rojo sourcemap dev.project.json -o sourcemap.json")
start_process( start_process(
"luau-lsp analyze --base-luaurc=.luaurc --sourcemap=sourcemap.json --settings=luau_lsp_settings.json --no-strict-dm-types --ignore Packages/**/*.lua --ignore Packages/**/*.luau lib/" "luau-lsp analyze --base-luaurc=.luaurc --sourcemap=sourcemap.json --settings=luau_lsp_settings.json --no-strict-dm-types --ignore Packages/**/*.lua --ignore Packages/**/*.luau --ignore lib/jecs.luau lib/"
) )

View file

@ -12,3 +12,4 @@ end
start_process("lune run analyze") start_process("lune run analyze")
start_process("stylua lib/") start_process("stylua lib/")
start_process("selene lib/") start_process("selene lib/")
start_process("luau test/tests.luau")

View file

@ -21,10 +21,8 @@
"mode": "relativeToFile", "mode": "relativeToFile",
"fileAliases": { "fileAliases": {
"@jecs_utils": "lib", "@jecs_utils": "lib",
"@jecs": "lib/jecs",
"@testkit": "test/testkit" "@testkit": "test/testkit"
},
"directoryAliases": {
"@pkg": "Packages/"
} }
} }
}, },

View file

@ -5,7 +5,7 @@
"ReplicatedStorage": { "ReplicatedStorage": {
"Packages": { "Packages": {
"$path": "Packages", "$className": "Folder",
"jecs_utils": { "jecs_utils": {
"$path": "lib" "$path": "lib"
} }

View file

@ -61,7 +61,9 @@ local function collect<D, T...>(event: signal_like<D, T...>): (() -> (number, T.
end end
end end
local disconnect = event:Connect(function(...) local connect = event.Connect or event.connect
assert(connect ~= nil, "Signal is missing a Connect function - is it really a signal?")
local disconnect = connect(event, function(...)
table.insert(storage, { ... }) table.insert(storage, { ... })
mt.__iter = iter :: any mt.__iter = iter :: any
end) end)

View file

@ -1,10 +1,10 @@
--!strict --!strict
--!optimize 2 --!optimize 2
local jecs = require("@pkg/jecs") local jecs = require("./jecs")
type entity<T = nil> = jecs.Entity<T> type entity<T = nil> = jecs.Entity<T>
type id<T = nil> = jecs.Id<T> type id<T = nil> = jecs.Id<T>
local world = require("./world").get() local WORLD = require("./world").get
--- `map<component_id, array<entity_id>>` --- `map<component_id, array<entity_id>>`
local add_commands: { [id]: { entity } } = {} local add_commands: { [id]: { entity } } = {}
@ -15,7 +15,7 @@ local remove_commands: { [id]: { entity } } = {}
--- `array<entity_id>` --- `array<entity_id>`
local delete_commands: { entity } = {} local delete_commands: { entity } = {}
type command_buffer = { export type command_buffer = {
--- Execute all buffered commands and clear the buffer --- Execute all buffered commands and clear the buffer
flush: () -> (), flush: () -> (),
@ -30,48 +30,46 @@ type command_buffer = {
} }
local function flush() local function flush()
local adds = add_commands local world = WORLD()
local sets = set_commands
local removes = remove_commands for _, entity in delete_commands do
local deletes = delete_commands world:delete(entity)
end
for component, entities in add_commands do
for _, entity in entities do
if delete_commands[entity] then
continue
end
world:add(entity, component)
end
end
table.clear(add_commands) table.clear(add_commands)
for component, entities in set_commands do
for entity, value in entities do
if delete_commands[entity] then
continue
end
world:set(entity, component, value)
end
end
table.clear(set_commands) table.clear(set_commands)
for component, entities in remove_commands do
for _, entity in entities do
if delete_commands[entity] then
continue
end
world:remove(entity, component)
end
end
table.clear(remove_commands) table.clear(remove_commands)
table.clear(delete_commands) table.clear(delete_commands)
for _, id in deletes do
world:delete(id)
end
for component, ids in adds do
for _, id in ids do
if deletes[id] then
continue
end
world:add(id, component)
end
end
for component, ids in sets do
for id, value in ids do
if deletes[id] then
continue
end
world:set(id, component, value)
end
end
for component, ids in removes do
for _, id in ids do
if deletes[id] then
continue
end
world:remove(id, component)
end
end
end end
local function add(entity: entity, component: id) local function add(entity: entity, component: id)

View file

@ -1,10 +1,10 @@
--!strict --!strict
--!optimize 2 --!optimize 2
local jecs = require("@pkg/jecs") local jecs = require("./jecs")
export type entity<T = nil> = jecs.Entity<T> type entity<T = nil> = jecs.Entity<T>
export type id<T = nil> = entity<T> | jecs.Pair type id<T = nil> = entity<T> | jecs.Pair
local world = require("./world").get() local world = require("./world").get
type interface = { type interface = {
__index: interface, __index: interface,
@ -44,34 +44,34 @@ function handle.new(entity: entity)
end end
function handle:has(...: id): boolean function handle:has(...: id): boolean
return world:has(self.entity, ...) return world():has(self.entity, ...)
end end
handle.get = function(self: handle, a: id, b: id?, c: id?, d: id?) handle.get = function(self: handle, a: id, b: id?, c: id?, d: id?)
return world:get(self.entity, a, b :: any, c :: any, d :: any) return world():get(self.entity, a, b :: any, c :: any, d :: any)
end :: any end :: any
function handle:add<T>(id: id<T>): handle function handle:add<T>(id: id<T>): handle
world:add(self.entity, id) world():add(self.entity, id)
return self return self
end end
function handle:set<T>(id: id<T>, value: T): handle function handle:set<T>(id: id<T>, value: T): handle
world:set(self.entity, id, value) world():set(self.entity, id, value)
return self return self
end end
function handle:remove(id: id): handle function handle:remove(id: id): handle
world:remove(self.entity, id) world():remove(self.entity, id)
return self return self
end end
function handle:delete() function handle:delete()
world:delete(self.entity) world():delete(self.entity)
end end
function handle:id(): entity function handle:id(): entity
return self.entity return self.entity
end end
return handle return handle.new

View file

@ -1,12 +1,19 @@
--!strict --!strict
--!optimize 2 --!optimize 2
local jecs = require("./jecs")
local WORLD = require("./world") local WORLD = require("./world")
local collect = require("./collect") local collect = require("./collect")
export type collect_signal_like<T...> = collect.signal_like<any, T...>
export type collect_verbose_signal_like<D, T...> = collect.signal_like<D, T...>
local command_buffer = require("./command_buffer") local command_buffer = require("./command_buffer")
export type command_buffer = command_buffer.command_buffer
local handle = require("./handle") local handle = require("./handle")
local jecs = require("@pkg/jecs") export type handle = handle.handle
local ref = require("./ref") local ref = require("./ref")
local replicator = require("./replicator") local replicator = require("./replicator")
export type replicator = replicator.replicator
export type changes = replicator.changes
--- Set the world for all utilities. --- Set the world for all utilities.
--- Should be called once per context before any utility is used. --- Should be called once per context before any utility is used.

1895
lib/jecs.luau Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
--!strict --!strict
--!optimize 2 --!optimize 2
local handle = require("./handle") local handle = require("./handle")
local world = require("./world").get() local WORLD = require("./world").get
local refs = {} local refs = {}
@ -11,8 +11,9 @@ local refs = {}
--- @param key any --- @param key any
--- @return handle --- @return handle
local function ref(key: any): handle.handle local function ref(key: any): handle.handle
local world = WORLD()
if not key then if not key then
return handle.new(world:entity()) return handle(world:entity())
end end
local entity = refs[key] local entity = refs[key]
@ -21,7 +22,7 @@ local function ref(key: any): handle.handle
refs[key] = entity refs[key] = entity
end end
return handle.new(entity) return handle(entity)
end end
return ref return ref

View file

@ -1,11 +1,11 @@
--!strict --!strict
--!optimize 2 --!optimize 2
local jecs = require("@pkg/jecs") local jecs = require("./jecs")
type entity<T = nil> = jecs.Entity<T> type entity<T = nil> = jecs.Entity<T>
type i53 = number type i53 = number
local ref = require("./ref") local ref = require("./ref")
local world = require("./world").get() local WORLD = require("./world").get
--- A replicator keeps track of all entities with the passed components and their values - --- A replicator keeps track of all entities with the passed components and their values -
--- whenever a component is changed (add, change, remove) and the replicator listens to it, it's also changed within the contained raw data.\ --- whenever a component is changed (add, change, remove) and the replicator listens to it, it's also changed within the contained raw data.\
@ -122,6 +122,7 @@ export type changes = {
--- @param ... entity --- @param ... entity
--- @return replicator --- @return replicator
local function replicator(...: entity): replicator local function replicator(...: entity): replicator
local world = WORLD()
local components = { ... } local components = { ... }
-- don't index a changes table start -- don't index a changes table start

View file

@ -1,6 +1,6 @@
--!strict --!strict
--!optimize 2 --!optimize 2
local jecs = require("@pkg/jecs") local jecs = require("./jecs")
local WORLD: jecs.World local WORLD: jecs.World

View file

@ -3,7 +3,7 @@
"LuauTinyControlFlowAnalysis": "true" "LuauTinyControlFlowAnalysis": "true"
}, },
"luau-lsp.require.mode": "relativeToFile", "luau-lsp.require.mode": "relativeToFile",
"luau-lsp.require.directoryAliases": { "luau-lsp.require.fileAliases": {
"@pkg": "Packages/" "@jecs": "lib/jecs"
} }
} }

View file

@ -1 +1,2 @@
std = "selene_definitions" std = "selene_definitions"
exclude = ["lib/jecs.luau"]

View file

96
test/signal.luau Normal file
View file

@ -0,0 +1,96 @@
-- licensed under MIT
-- @author jackdotink
-- https://github.com/red-blox/Util/blob/main/libs/Signal/Signal.luau
-- adapted to work in pure luau
type node<T...> = {
next: node<T...>?,
callback: (T...) -> (),
}
export type signal<T...> = {
root: node<T...>?,
connect: (self: signal<T...>, Callback: (T...) -> ()) -> () -> (),
wait: (self: signal<T...>) -> T...,
once: (self: signal<T...>, Callback: (T...) -> ()) -> () -> (),
fire: (self: signal<T...>, T...) -> (),
disconnect_all: (self: signal<T...>) -> (),
}
local Signal = {}
Signal.__index = Signal
-- Extracted this function from Connect as it results in the closure
-- made in Connect using less memory because this function can be static
local function disconnect<T...>(self: signal<T...>, Node: node<T...>)
if self.root == Node then
self.root = Node.next
else
local Current = self.root
while Current do
if Current.next == Node then
Current.next = Node.next
break
end
Current = Current.next
end
end
end
function Signal.connect<T...>(self: signal<T...>, Callback: (T...) -> ()): () -> ()
local node = {
next = self.root,
callback = Callback,
}
self.root = node
return function()
disconnect(self, node)
end
end
function Signal.wait<T...>(self: signal<T...>): T...
local Thread = coroutine.running()
local Disconnect
Disconnect = self:connect(function(...)
Disconnect()
coroutine.resume(Thread, ...)
end)
return coroutine.yield()
end
function Signal.once<T...>(self: signal<T...>, Callback: (T...) -> ()): () -> ()
local Disconnect
Disconnect = self:connect(function(...)
Disconnect()
Callback(...)
end)
return Disconnect
end
function Signal.fire<T...>(self: signal<T...>, ...: T...)
local Current = self.root
while Current do
Current.callback(...)
Current = Current.next
end
end
function Signal.disconnect_all<T...>(self: signal<T...>)
self.root = nil
end
return function<T...>(): signal<T...>
return setmetatable({
root = nil,
}, Signal) :: any
end

237
test/tests.luau Normal file
View file

@ -0,0 +1,237 @@
--!strict
-- stylua: ignore start
local jecs = require("@jecs")
local jecs_utils = require("@jecs_utils")
local testkit = require("@testkit")
local collect = jecs_utils.collect
local handle = jecs_utils.handle
local replicator = jecs_utils.replicator
local ref = jecs_utils.ref
local command_buffer = jecs_utils.command_buffer
local signal = require("./signal")
local BENCH, START = testkit.benchmark()
local TEST, CASE, CHECK, FINISH, SKIP, FOCUS = testkit.test()
TEST("jecs_utils.collect()", function()
do CASE "collects"
local sig: signal.signal<number> = signal()
local flush = collect(sig)
local should = {}
for idx = 100, 1, -1 do
local n = math.random()
should[idx] = n
sig:fire(n)
end
for idx, n in flush do
CHECK(should[idx] == n)
end
end
end)
TEST("jecs_utils.handle()", function()
do CASE "has"
local world = jecs.World.new()
jecs_utils.initialize(world)
local entity = world:entity()
local tag = world:entity()
world:add(entity, tag)
CHECK(handle(entity):has(tag))
end
do CASE "get"
local world = jecs.World.new()
jecs_utils.initialize(world)
local entity = world:entity()
local component = world:component()
world:set(entity, component, 50)
CHECK(handle(entity):get(component) == 50)
end
do CASE "add"
local world = jecs.World.new()
jecs_utils.initialize(world)
local entity = world:entity()
local tag = world:entity()
handle(entity):add(tag)
CHECK(world:has(entity, tag))
end
do CASE "set"
local world = jecs.World.new()
jecs_utils.initialize(world)
local entity = world:entity()
local component = world:component()
handle(entity):set(component, 50)
CHECK(world:get(entity, component) == 50)
end
do CASE "remove"
local world = jecs.World.new()
jecs_utils.initialize(world)
local entity = world:entity()
local component = world:component()
handle(entity):set(component, 50)
CHECK(world:get(entity, component) == 50)
end
do CASE "delete"
local world = jecs.World.new()
jecs_utils.initialize(world)
local entity = world:entity()
handle(entity):delete()
CHECK(not world:contains(entity))
end
end)
TEST("jecs_utils.ref()", function()
do CASE "ref(abc) == ref(abc)"
local world = jecs.World.new()
jecs_utils.initialize(world)
local a: number = ref(1234):id()
local b: number = ref(1234):id()
CHECK(a == b)
end
end)
TEST("jecs_utils.replicator()", function()
do CASE "propagates difference"
local world = jecs.World.new()
local tag = world:entity()
local component: jecs.Entity<number> = world:component()
local entity1 = world:entity()
local entity2 = world:entity()
jecs_utils.initialize(world)
local rep = replicator(component, tag)
world:add(entity1, tag)
world:set(entity2, component, 50)
local difference: jecs_utils.changes = rep.calculate_difference() :: any
CHECK(difference ~= nil)
local world2 = jecs.World.new()
local component2: jecs.Entity<number> = world2:component()
local tag2 = world2:entity()
jecs_utils.initialize(world2)
local rep2 = replicator(component2, tag2)
rep2.apply_difference(difference)
CHECK(ref(`replicated-{entity1}`):has(tag2))
CHECK(ref(`replicated-{entity2}`):get(component2) == 50)
end
do CASE "propagates full data"
local world = jecs.World.new()
local tag = world:entity()
local component: jecs.Entity<number> = world:component()
local entity1 = world:entity()
local entity2 = world:entity()
jecs_utils.initialize(world)
local rep = replicator(component, tag)
world:add(entity1, tag)
world:set(entity2, component, 50)
local full_data = rep.get_full_data()
CHECK(full_data ~= nil)
local world2 = jecs.World.new()
local component2: jecs.Entity<number> = world2:component()
local tag2 = world2:entity()
jecs_utils.initialize(world2)
local rep2 = replicator(component2, tag2)
rep2.apply_difference(full_data)
CHECK(ref(`replicated-{entity1}`):has(tag2))
CHECK(ref(`replicated-{entity2}`):get(component2) == 50)
end
end)
TEST("jecs_utils.command_buffer", function()
do CASE "add"
local world = jecs.World.new()
jecs_utils.initialize(world)
local tag = world:entity()
local entity = world:entity()
command_buffer.add(entity, tag)
CHECK(not world:has(entity, tag))
command_buffer.flush()
CHECK(world:has(entity, tag))
end
do CASE "set"
local world = jecs.World.new()
jecs_utils.initialize(world)
local component = world:component()
local entity = world:entity()
command_buffer.set(entity, component, 50)
CHECK(not world:has(entity, component))
command_buffer.flush()
CHECK(world:get(entity, component) == 50)
end
do CASE "remove"
local world = jecs.World.new()
jecs_utils.initialize(world)
local component = world:component()
local entity = world:entity()
world:set(entity, component, 50)
command_buffer.remove(entity, component)
CHECK(world:has(entity, component))
command_buffer.flush()
CHECK(not world:has(entity, component))
end
do CASE "delete"
local world = jecs.World.new()
jecs_utils.initialize(world)
local entity = world:entity()
command_buffer.delete(entity)
command_buffer.flush()
CHECK(not world:contains(entity))
end
end)
FINISH()
-- stylua: ignore end

View file

@ -15,4 +15,3 @@ include = [
] ]
[dependencies] [dependencies]
jecs = "ukendio/jecs@0.2.10"