From c83038583c543d7a4613aff4e6404bf06fa8a3f3 Mon Sep 17 00:00:00 2001 From: Mark Marks Date: Mon, 26 Aug 2024 18:21:07 +0200 Subject: [PATCH] feat: Finish sapphire-ecr, update squash in sapphire-net to v2.4.0, bump sapphire-net to v0.1.1 --- .github/workflows/ci.yml | 8 +- .gitignore | 1 - aftman.toml | 13 - crates/sapphire-data/wally.lock | 33 ++ crates/sapphire-ecr/lib/init.luau | 257 +++++++++++++-- crates/sapphire-ecr/wally.lock | 28 ++ crates/sapphire-jecs/wally.lock | 23 ++ crates/sapphire-lifecycles/wally.lock | 8 + crates/sapphire-logging/wally.lock | 18 ++ crates/sapphire-net/lib/data_types.luau | 169 +++++++++- crates/sapphire-net/lib/init.luau | 299 +++++++----------- crates/sapphire-net/lib/types.luau | 30 ++ crates/sapphire-net/wally.lock | 13 + crates/sapphire-net/wally.toml | 4 +- crates/sapphire/wally.lock | 13 + .../controllers/systems/replication.luau | 21 ++ .../controllers/systems/update_position.luau | 16 + dev/client/main.client.luau | 13 +- dev/server/main.server.luau | 9 +- dev/server/services/net_test.luau | 1 - dev/server/services/spawn_parts.luau | 27 ++ dev/server/services/systems/replication.luau | 30 ++ .../services/systems/update_position.luau | 18 ++ dev/shared/components.luau | 9 + dev/shared/events.luau | 8 + rokit.toml | 13 + selene.toml | 17 +- wally.lock | 48 +++ wally.toml | 2 +- 29 files changed, 886 insertions(+), 263 deletions(-) delete mode 100644 aftman.toml create mode 100644 crates/sapphire-data/wally.lock create mode 100644 crates/sapphire-ecr/wally.lock create mode 100644 crates/sapphire-jecs/wally.lock create mode 100644 crates/sapphire-lifecycles/wally.lock create mode 100644 crates/sapphire-logging/wally.lock create mode 100644 crates/sapphire-net/wally.lock create mode 100644 crates/sapphire/wally.lock create mode 100644 dev/client/controllers/systems/replication.luau create mode 100644 dev/client/controllers/systems/update_position.luau create mode 100644 dev/server/services/spawn_parts.luau create mode 100644 dev/server/services/systems/replication.luau create mode 100644 dev/server/services/systems/update_position.luau create mode 100644 dev/shared/components.luau create mode 100644 rokit.toml create mode 100644 wally.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c44f36..c581311 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Install Aftman - uses: ok-nick/setup-aftman@v0.4.2 + - name: Install Rokit + uses: CompeyDev/setup-rokit@v0.1.2 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -31,8 +31,8 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Install Aftman - uses: ok-nick/setup-aftman@v0.4.2 + - name: Install Rokit + uses: CompeyDev/setup-rokit@v0.1.2 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 10b0ece..98e41b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ **/Packages -wally.lock sourcemap.json **/*.rbx[lm]* roblox.yml diff --git a/aftman.toml b/aftman.toml deleted file mode 100644 index 402fe89..0000000 --- a/aftman.toml +++ /dev/null @@ -1,13 +0,0 @@ -# This file lists tools managed by Aftman, a cross-platform toolchain manager. -# For more information, see https://github.com/LPGhatguy/aftman - -# To add a new tool, add an entry to this table. -[tools] -selene = "kampfkarren/selene@0.27.1" -luau-lsp = "JohnnyMorganz/luau-lsp@1.32.1" -lune = "lune-org/lune@0.8.6" -wally = "UpliftGames/wally@0.3.2" -stylua = "JohnnyMorganz/stylua@0.20.0" -rojo = "rojo-rbx/rojo@7.4.2" -wally-package-types = "JohnnyMorganz/wally-package-types@1.3.2" -# rojo = "rojo-rbx/rojo@6.2.0" diff --git a/crates/sapphire-data/wally.lock b/crates/sapphire-data/wally.lock new file mode 100644 index 0000000..fb2114b --- /dev/null +++ b/crates/sapphire-data/wally.lock @@ -0,0 +1,33 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "ffrostflame/keyform" +version = "0.2.2" +dependencies = [["Signal", "ffrostflame/luausignal@0.1.3"], ["TableKit", "ffrostflame/tablekit@0.2.4"]] + +[[package]] +name = "ffrostflame/luausignal" +version = "0.1.3" +dependencies = [] + +[[package]] +name = "ffrostflame/tablekit" +version = "0.2.4" +dependencies = [] + +[[package]] +name = "mark-marks/sapphire-data" +version = "0.1.0" +dependencies = [["keyform", "ffrostflame/keyform@0.2.2"], ["signal", "red-blox/signal@2.0.2"]] + +[[package]] +name = "red-blox/signal" +version = "2.0.2" +dependencies = [["Spawn", "red-blox/spawn@1.1.0"]] + +[[package]] +name = "red-blox/spawn" +version = "1.1.0" +dependencies = [] diff --git a/crates/sapphire-ecr/lib/init.luau b/crates/sapphire-ecr/lib/init.luau index 6ffe28c..3b7c0fa 100644 --- a/crates/sapphire-ecr/lib/init.luau +++ b/crates/sapphire-ecr/lib/init.luau @@ -15,36 +15,34 @@ export type system = { loop_type: loop_type?, } -local World = {} +local SapphireEcr = {} --- @readonly -World.identifier = "sapphire-ecr" ---- @readonly -World.methods = {} +SapphireEcr.identifier = "sapphire-ecr" -World.registry = ecr.registry() +SapphireEcr.registry = ecr.registry() --- @readonly -World.stepped_systems = {} :: { system } +SapphireEcr.stepped_systems = {} :: { system } --- @readonly -World.heartbeat_systems = {} :: { system } +SapphireEcr.heartbeat_systems = {} :: { system } --- @readonly -World.render_stepped_systems = {} :: { system } +SapphireEcr.render_stepped_systems = {} :: { system } -function World.extension() +function SapphireEcr.extension() RunService.Stepped:Connect(function(delta_time) - for _, system in World.stepped_systems do + for _, system in SapphireEcr.stepped_systems do fast_spawn(system.runner, delta_time) end end) RunService.Heartbeat:Connect(function(delta_time) - for _, system in World.heartbeat_systems do + for _, system in SapphireEcr.heartbeat_systems do fast_spawn(system.runner, delta_time) end end) if RunService:IsClient() then RunService.RenderStepped:Connect(function(delta_time) - for _, system in World.render_stepped_systems do + for _, system in SapphireEcr.render_stepped_systems do fast_spawn(system.runner, delta_time) end end) @@ -67,10 +65,10 @@ local function create_spawner(...) local function spawn(...) local passed = { ... } - local entity = World.spawn_entity() + local entity = SapphireEcr.spawn_entity() - for index in components do - World.registry:add(entity, passed[index]) + for index, component in components do + SapphireEcr.registry:set(entity, component, passed[index]) end return entity @@ -78,10 +76,10 @@ local function create_spawner(...) local function spawn_with_handle(...) local passed = { ... } - local entity = World.spawn_entity_with_handle() + local entity = SapphireEcr.spawn_entity_with_handle() - for index in components do - entity:add(passed[index]) + for index, component in components do + entity:set(component, passed[index]) end return entity @@ -102,21 +100,202 @@ end --- ``` --- @param ... T... -- Components to use. --- @return spawner -function World.create_spawner(...: T...): spawner +function SapphireEcr.create_spawner(...: T...): spawner return create_spawner(...) end --- Creates an entity and returns it's id. --- @return ecr.entity -function World.spawn_entity(): ecr.entity - return World.registry:create() +function SapphireEcr.spawn_entity(): ecr.entity + return SapphireEcr.registry:create() end --- Creates an entity and returns a handle to it. --- @return ecr.Handle -function World.spawn_entity_with_handle(): ecr.Handle - local entity = World.spawn_entity() - return World.registry:handle(entity) +function SapphireEcr.spawn_entity_with_handle(): ecr.Handle + local entity = SapphireEcr.spawn_entity() + return SapphireEcr.registry:handle(entity) +end + +export type raw_data = { + [ecr.entity]: { [number]: unknown }, +} + +--- 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.\ +--- The developer can then calculate the difference on the server and send it to the client every time, +--- on which the difference is then applied to the registry.\ +--- Albeit it's called a replicator, it doesn't replicate the data by itself. +--- It allows the developer to use any networking libary to replicate the changes. +--- ```luau +--- -- server +--- local replicator = sapphire_ecr.create_replicator(component_a, component_b, ...) +--- +--- function singleton.system() +--- return function() +--- local difference = replicator.calculate_difference() --> buffer +--- -- There might not be any difference +--- if not difference then +--- return +--- end +--- data_replication_event.send_to_all(difference) +--- end +--- end +--- ``` +--- ```luau +--- -- client +--- local replicator = sapphire_ecr.create_replicator(component_a, component_b, ...) +--- +--- function singleton.system() +--- return function() +--- for _, difference in data_replication_event.poll() +--- replicator.apply_difference(difference) +--- end +--- end +--- end +--- ``` +export type replicator = { + --- Gets the full data representing the entire registry. + --- Useful for initial replication to every player. + --- ```luau + --- local replicator = sapphire_ecr.create_replicator(component_a, component_b, ...) + --- + --- Players.PlayerAdded:Connect(function(player) + --- data_replication_event.send_to(player, replicator.get_full_data()) + --- end) + --- ``` + --- @return raw_data + get_full_data: () -> raw_data, + --- Calculates the difference between last sent data and currently stored data. + --- ```luau + --- local replicator = sapphire_ecr.create_replicator(component_a, component_b, ...) + --- + --- function singleton.system() + --- return function() + --- local difference = replicator.calculate_difference() --> buffer + --- -- There might not be any difference + --- if not difference then + --- return + --- end + --- data_replication_event.send_to_all(difference) + --- end + --- end + --- ``` + --- @return raw_data? -- There might not be any difference + calculate_difference: () -> raw_data?, + --- Applies the difference to the current data. + --- ```luau + --- local replicator = sapphire_ecr.create_replicator(component_a, component_b, ...) + --- + --- function singleton.system() + --- return function() + --- for _, difference in data_replication_event.poll() + --- replicator.apply_difference(difference) + --- end + --- end + --- end + --- ``` + --- @param difference raw_data + apply_difference: (difference: raw_data) -> (), +} + +--- Creates a "replicator."\ +--- 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.\ +--- The developer can then calculate the difference on the server and send it to the client every time, +--- on which the difference is then applied to the registry.\ +--- Albeit it's called a replicator, it doesn't replicate the data by itself. +--- It allows the developer to use any networking libary to replicate the changes. +--- ```luau +--- -- server +--- local replicator = sapphire_ecr.create_replicator(component_a, component_b, ...) +--- +--- function singleton.system() +--- return function() +--- local difference = replicator.calculate_difference() --> buffer +--- -- There might not be any difference +--- if not difference then +--- return +--- end +--- data_replication_event.send_to_all(difference) +--- end +--- end +--- ``` +--- ```luau +--- -- client +--- local replicator = sapphire_ecr.create_replicator(component_a, component_b, ...) +--- +--- function singleton.system() +--- return function() +--- for _, difference in data_replication_event.poll() +--- replicator.apply_difference(difference) +--- end +--- end +--- end +--- ``` +--- @param ... any -- Components to keep track of +--- @return replicator +function SapphireEcr.create_replicator(...): replicator + local components = { ... } + local changes: raw_data = {} + local raw_data: raw_data = {} + + for _, component: number in components do + SapphireEcr.registry:on_add(component):connect(function(entity, added_component) + if not raw_data[entity] then + raw_data[entity] = {} + changes[entity] = {} + end + raw_data[entity][component] = added_component + changes[entity][component] = added_component + end) + SapphireEcr.registry:on_change(component):connect(function(entity, changed_component) + raw_data[entity][component] = changed_component + if not changes[entity] then + changes[entity] = {} + end + changes[entity][component] = changed_component + end) + SapphireEcr.registry:on_remove(component):connect(function(entity) + raw_data[entity][component] = nil + if not changes[entity] then + changes[entity] = {} + end + changes[entity][component] = nil + end) + end + + local function get_full_data(): raw_data + return raw_data + end + + local function calculate_difference(): raw_data? + local difference = changes + changes = {} + return if next(difference) ~= nil then difference else nil + end + + local function apply_difference(difference: raw_data) + for entity, entity_components in difference do + if not SapphireEcr.registry:contains(entity) then + SapphireEcr.registry:create(entity) + end + + for component, value in entity_components do + local existing_value = SapphireEcr.registry:try_get(entity, component :: any) + if existing_value == value then + continue + end + SapphireEcr.registry:set(entity, component :: unknown, value) + end + end + end + + return { + get_full_data = get_full_data, + calculate_difference = calculate_difference, + apply_difference = apply_difference, + } end export type singleton = sapphire.singleton & { @@ -125,8 +304,11 @@ export type singleton = sapphire.singleton & { loop_type: loop_type?, } -function World.methods.system(singleton: singleton) - local method: (registry: ecr.Registry) -> (delta_time: number) -> () = singleton.scheduled_system +--- @readonly +SapphireEcr.methods = {} + +function SapphireEcr.methods.system(singleton: sapphire.singleton) + local method: (registry: ecr.Registry) -> (delta_time: number) -> () = singleton.system local priority: number = singleton.priority or 1 :: any local loop_type: loop_type = singleton.loop_type or "Stepped" @@ -144,15 +326,28 @@ function World.methods.system(singleton: singleton) ) end - local systems: { system } = loop_type == "Stepped" and World.stepped_systems - or loop_type == "Heartbeat" and World.heartbeat_systems - or World.render_stepped_systems + local systems: { system } = loop_type == "Stepped" and SapphireEcr.stepped_systems + or loop_type == "Heartbeat" and SapphireEcr.heartbeat_systems + or SapphireEcr.render_stepped_systems - local runner = method(World.registry) + local runner = method(SapphireEcr.registry) table.insert(systems, { runner = runner, priority = priority }) table.sort(systems, function(a, b) return a.priority > b.priority end) end -return World +--- Reexport of ECR +SapphireEcr.ecr = ecr + +export type entity = ecr.entity +export type Signal = ecr.Signal +export type Connection = ecr.Connection +export type Handle = ecr.Handle +export type View = ecr.View +export type Observer = ecr.Observer +export type Group = ecr.Group +export type Registry = ecr.Registry +export type Queue = ecr.Queue + +return SapphireEcr diff --git a/crates/sapphire-ecr/wally.lock b/crates/sapphire-ecr/wally.lock new file mode 100644 index 0000000..c198298 --- /dev/null +++ b/crates/sapphire-ecr/wally.lock @@ -0,0 +1,28 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "centau/ecr" +version = "0.8.0" +dependencies = [] + +[[package]] +name = "mark-marks/sapphire" +version = "0.1.1" +dependencies = [["spawn", "red-blox/spawn@1.1.0"]] + +[[package]] +name = "mark-marks/sapphire-ecr" +version = "0.1.0" +dependencies = [["ecr", "centau/ecr@0.8.0"], ["sapphire", "mark-marks/sapphire@0.1.1"], ["signal", "red-blox/signal@2.0.2"]] + +[[package]] +name = "red-blox/signal" +version = "2.0.2" +dependencies = [["Spawn", "red-blox/spawn@1.1.0"]] + +[[package]] +name = "red-blox/spawn" +version = "1.1.0" +dependencies = [] diff --git a/crates/sapphire-jecs/wally.lock b/crates/sapphire-jecs/wally.lock new file mode 100644 index 0000000..7d8da49 --- /dev/null +++ b/crates/sapphire-jecs/wally.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "mark-marks/sapphire-jecs" +version = "0.1.0" +dependencies = [["jecs", "ukendio/jecs@0.2.5"], ["signal", "red-blox/signal@2.0.2"]] + +[[package]] +name = "red-blox/signal" +version = "2.0.2" +dependencies = [["Spawn", "red-blox/spawn@1.1.0"]] + +[[package]] +name = "red-blox/spawn" +version = "1.1.0" +dependencies = [] + +[[package]] +name = "ukendio/jecs" +version = "0.2.5" +dependencies = [] diff --git a/crates/sapphire-lifecycles/wally.lock b/crates/sapphire-lifecycles/wally.lock new file mode 100644 index 0000000..07ad14a --- /dev/null +++ b/crates/sapphire-lifecycles/wally.lock @@ -0,0 +1,8 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "mark-marks/sapphire-lifecycles" +version = "0.1.0" +dependencies = [] diff --git a/crates/sapphire-logging/wally.lock b/crates/sapphire-logging/wally.lock new file mode 100644 index 0000000..218cb05 --- /dev/null +++ b/crates/sapphire-logging/wally.lock @@ -0,0 +1,18 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "mark-marks/sapphire-logging" +version = "0.1.0" +dependencies = [["signal", "red-blox/signal@2.0.2"]] + +[[package]] +name = "red-blox/signal" +version = "2.0.2" +dependencies = [["Spawn", "red-blox/spawn@1.1.0"]] + +[[package]] +name = "red-blox/spawn" +version = "1.1.0" +dependencies = [] diff --git a/crates/sapphire-net/lib/data_types.luau b/crates/sapphire-net/lib/data_types.luau index ffe6197..0f33ff6 100644 --- a/crates/sapphire-net/lib/data_types.luau +++ b/crates/sapphire-net/lib/data_types.luau @@ -2,8 +2,151 @@ --!native --!optimize 2 local squash = require(script.Parent.Parent.squash) +local exports: any -return table.freeze({ +export type channel = squash.Cursor & { + references: { Instance }, +} + +export type serdes = { + ser: (channel: channel, value: T) -> (), + des: (channel: channel) -> T, +} + +local function sum(x: T): number + local n = 0 + for _ in x :: any do + n += 1 + end + return n +end + +local function write_reference(references: { Instance }, instance: Instance): number + local id = #references + 1 + references[id] = instance + return id +end + +local _vlq_serdes = squash.vlq() +local vlq_ser = _vlq_serdes.ser +local vlq_des = _vlq_serdes.des + +local instance_cache = { + ser = function(channel: channel, value: Instance) + local ref = write_reference(channel.references, value) + vlq_ser(channel, ref) + end, + des = function(channel: channel): Instance? + local refs = channel.references + if not refs then + return nil + end + + local id = vlq_des(channel) + local ref = refs[id] + + if typeof(ref) == "Instance" then + return ref + end + return nil + end, +} + +local function instance_serdes(): serdes + return instance_cache :: any +end + +local nil_cache: serdes = { + ser = function() end, + des = function(): nil + return nil + end, +} + +local function nil_serdes(): serdes + return nil_cache +end + +local _u8_serdes = squash.uint(1) +local u8_ser = _u8_serdes.ser +local u8_des = _u8_serdes.des + +-- !! unfortunately, squash treats cursors like a stack +-- this means that data has to be written backwards so it can be read normally; +-- instead of doing | type | | data | we have to do | data | | type | +-- also has to store the length of tables so the packet id serdes upvalues will be used + +local function unknown_serialize(channel: channel, value: unknown) + local type_to_id_lookup = exports.type_to_id + local data_serdes = exports.types + + local data_type = typeof(value) + if data_type ~= "table" then + data_serdes[data_type]().ser(channel, value) + u8_ser(channel, type_to_id_lookup[data_type]) + return + end + + for k, v in value :: { [unknown]: unknown } do + unknown_serialize(channel, v) + unknown_serialize(channel, k) + end + vlq_ser(channel, sum(value)) + u8_ser(channel, type_to_id_lookup["table"]) +end + +local function unknown_deserialize(channel: channel): unknown + local id_to_type_lookup = exports.id_to_type + local data_serdes = exports.types + + local data_type = id_to_type_lookup[u8_des(channel)] + if data_type == nil then + return nil + end + if data_type ~= "table" then + return data_serdes[data_type]().des(channel) + end + + local n = vlq_des(channel) + + local reconstructed = {} + + for _ = 1, n do + local k = unknown_deserialize(channel) + local v = unknown_deserialize(channel) + reconstructed[k] = v + end + + return reconstructed +end + +local unknown_cache = { + ser = unknown_serialize, + des = unknown_deserialize, +} + +local function unknown_serdes(): serdes + return unknown_cache +end + +local function create_channel(size: number): channel + return { + Buf = buffer.create(size), + Pos = 0, + references = {}, + } +end + +local function create_channel_from_buffer(buf: buffer): channel + local cursor = squash.frombuffer(buf) + return { + Buf = cursor.Buf, + Pos = cursor.Pos, + references = {}, + } +end + +exports = table.freeze({ types = table.freeze({ boolean = squash.boolean, uint = squash.uint, @@ -80,6 +223,10 @@ return table.freeze({ end, Vector2int16 = squash.Vector2int16, Vector3int16 = squash.Vector3int16, + ["Instance"] = instance_serdes, + unknown = unknown_serdes, + ["nil"] = nil_serdes, + literal = squash.literal, base_conversion = table.freeze({ alphabet = squash.string.alphabet, convert = squash.string.convert, @@ -144,7 +291,10 @@ return table.freeze({ [43] = "UDim2", [44] = "Vector2int16", [45] = "Vector3int16", - [46] = "EOF", -- End Of File + [46] = "Instance", + [47] = "nil", + [48] = "literal", + [49] = "EOF", -- End Of File }), type_to_id = table.freeze({ boolean = 1, @@ -192,6 +342,19 @@ return table.freeze({ UDim2 = 43, Vector2int16 = 44, Vector3int16 = 45, - EOF = 46, -- End Of File + Instance = 46, + ["nil"] = 47, + literal = 48, + EOF = 49, -- End Of File }), + channel = create_channel, + channel_from_buffer = create_channel_from_buffer, }) + +return exports :: { + types: { [string]: serdes }, + id_to_type: { [number]: string }, + type_to_id: { [string]: number }, + channel: (size: number) -> channel, + channel_from_buffer: (buf: buffer) -> channel, +} diff --git a/crates/sapphire-net/lib/init.luau b/crates/sapphire-net/lib/init.luau index 0f0b701..4931df7 100644 --- a/crates/sapphire-net/lib/init.luau +++ b/crates/sapphire-net/lib/init.luau @@ -17,8 +17,6 @@ local types = require(script.types) local data_types = require(script.data_types) local data_serdes: { [string]: () -> squash.SerDes } = data_types.types :: any -local id_to_type_lookup: { [number]: string } = data_types.id_to_type -local type_to_id_lookup: { [string]: number } = data_types.type_to_id local run_context: "server" | "client" = if RunService:IsServer() then "server" else "client" local fresh_channel_size: number = 1024 @@ -109,122 +107,74 @@ end -- EVENT BATCHES +type channel = data_types.channel +local function create_channel() + return data_types.channel(fresh_channel_size) +end + type reliability_type = "reliable" | "unreliable" local incoming_signals: { [number]: construct_signal.Signal } = table.create(fresh_channel_size) local incoming_queue: { [number]: queue } = table.create(fresh_channel_size) -- Only reliable events are batched, as unreliables have a size limit of ~900 bytes -local outgoing_client = squash.cursor(fresh_channel_size) -local outgoing_server: { [Player]: squash.Cursor } = {} +local outgoing_client = create_channel() +local outgoing_server: { [Player]: channel } = {} -- Serdes upvalue localizations -local _packet_id_serdes = squash.uint(2) +local _packet_id_serdes = squash.vlq() --squash.uint(2) local packet_id_ser = _packet_id_serdes.ser local packet_id_des = _packet_id_serdes.des -local _type_id_serdes = squash.uint(1) -local type_id_ser = _type_id_serdes.ser -local type_id_des = _type_id_serdes.des - -- DYNAMIC SERDES --- !! unfortunately, squash treats cursors like a stack --- this means that data has to be written backwards so it can be read normally; --- instead of doing | type | | data | we have to do | data | | type | --- also has to store the length of tables so the packet id serdes upvalues will be used - -local function sum(x: T): number - local n = 0 - for _ in x :: any do - n += 1 - end - return n -end - -local function dynamic_serialize(cursor: squash.Cursor, value: unknown, fresh: boolean?) - if fresh then - type_id_ser(cursor, type_to_id_lookup["EOF"]) - end - - local data_type = typeof(value) - if data_type ~= "table" then - data_serdes[data_type]().ser(cursor, value) - type_id_ser(cursor, type_to_id_lookup[data_type]) - return - end - - for k, v in value :: { [unknown]: unknown } do - dynamic_serialize(cursor, v, false) - dynamic_serialize(cursor, k, false) - end - packet_id_ser(cursor, sum(value)) - type_id_ser(cursor, type_to_id_lookup["table"]) -end - -local function dynamic_deserialize(cursor: squash.Cursor): unknown - local data_type = id_to_type_lookup[type_id_des(cursor)] - if data_type == "EOF" or data_type == nil then - return nil - end - if data_type ~= "table" then - return data_serdes[data_type]().des(cursor) - end - - local n = packet_id_des(cursor) - - local reconstructed = {} - - for _ = 1, n do - local k = dynamic_deserialize(cursor) - local v = dynamic_deserialize(cursor) - reconstructed[k] = v - end - - return reconstructed -end +local dynamic_serdes = data_serdes.unknown() +local dynamic_ser = dynamic_serdes.ser -- taken from squash, `tryRealloc` -- you can't merge cursors -local function alloc(cursor: squash.Cursor, bytes: number) - local b = cursor.Buf - local p = cursor.Pos +local function alloc(channel: channel, bytes: number) + local b = channel.Buf + local p = channel.Pos local len = buffer.len(b) if len < p + bytes then local exponent = math.ceil(math.log((bytes + p) / len, 1.5)) local new = buffer.create(len * 1.5 ^ exponent) buffer.copy(new, 0, b, 0) - cursor.Buf = new + channel.Buf = new end end -local function dump_into_batch(cursor: squash.Cursor, data: buffer) +local function dump_into_batch(channel: channel, data: buffer, refs: { Instance }) local len = buffer.len(data) - alloc(cursor, len) - buffer.copy(cursor.Buf, cursor.Pos, data, 0, len) - cursor.Pos += len + alloc(channel, len) + buffer.copy(channel.Buf, channel.Pos, data, 0, len) + channel.Pos += len + for id, ref in refs do + channel.references[id] = ref + end end -- SHARED -local function fire_client_reliable(player: Player, data: buffer) +local function fire_client_reliable(player: Player, data: buffer, refs: { Instance }) if not outgoing_server[player] then - outgoing_server[player] = squash.cursor(fresh_channel_size) + outgoing_server[player] = create_channel() end - dump_into_batch(outgoing_server[player], data) + dump_into_batch(outgoing_server[player], data, refs) end -local function fire_client_unreliable(player: Player, data: buffer) - unreliable_remote:FireClient(player, data) +local function fire_client_unreliable(player: Player, data: buffer, refs: { Instance }) + unreliable_remote:FireClient(player, data, #refs > 0 and refs or nil) end -local function fire_server_reliable(data: buffer) - dump_into_batch(outgoing_client, data) +local function fire_server_reliable(data: buffer, refs: { Instance }) + dump_into_batch(outgoing_client, data, refs) end -local function fire_server_unreliable(data: buffer) - unreliable_remote:FireServer(data) +local function fire_server_unreliable(data: buffer, refs: { Instance }) + unreliable_remote:FireServer(data, #refs > 0 and refs or nil) end -- REPLICATED VALUES @@ -344,10 +294,10 @@ local function construct_event( packet_id: number, ser: (cursor: squash.Cursor, value: any) -> () ) - local fire_client: (player: Player, data: buffer) -> () = if reliability_type == "reliable" + local fire_client: (player: Player, data: buffer, refs: { Instance }) -> () = if reliability_type == "reliable" then fire_client_reliable else fire_client_unreliable - local fire_server: (data: buffer) -> () = if reliability_type == "reliable" + local fire_server: (data: buffer, refs: { Instance }) -> () = if reliability_type == "reliable" then fire_server_reliable else fire_server_unreliable @@ -374,50 +324,50 @@ local function construct_event( }) function exports.send(data: any) - local cursor = squash.cursor(fresh_channel_size) - ser(cursor, data) - packet_id_ser(cursor, packet_id) - fire_server(squash.tobuffer(cursor)) + local channel = create_channel() + ser(channel, data) + packet_id_ser(channel, packet_id) + fire_server(squash.tobuffer(channel), channel.references) end if run_context == "server" then function exports.send_to(player: Player, data: any) - local cursor = squash.cursor(fresh_channel_size) - ser(cursor, data) - packet_id_ser(cursor, packet_id) - fire_client(player, squash.tobuffer(cursor)) + local channel = create_channel() + ser(channel, data) + packet_id_ser(channel, packet_id) + fire_client(player, squash.tobuffer(channel), channel.references) end function exports.send_to_all_except(except: Player, data: any) - local cursor = squash.cursor(fresh_channel_size) - ser(cursor, data) - packet_id_ser(cursor, packet_id) - local buf = squash.tobuffer(cursor) + local channel = create_channel() + ser(channel, data) + packet_id_ser(channel, packet_id) + local buf = squash.tobuffer(channel) for _, player in Players:GetPlayers() do if player == except then continue end - fire_client(player, buf) + fire_client(player, buf, channel.references) end end function exports.send_to_list(players: { Player }, data: any) - local cursor = squash.cursor(fresh_channel_size) - ser(cursor, data) - packet_id_ser(cursor, packet_id) - local buf = squash.tobuffer(cursor) + local channel = create_channel() + ser(channel, data) + packet_id_ser(channel, packet_id) + local buf = squash.tobuffer(channel) for _, player in players do - fire_client(player, buf) + fire_client(player, buf, channel.references) end end function exports.send_to_all(data: any) - local cursor = squash.cursor(fresh_channel_size) - ser(cursor, data) - packet_id_ser(cursor, packet_id) - local buf = squash.tobuffer(cursor) + local channel = create_channel() + ser(channel, data) + packet_id_ser(channel, packet_id) + local buf = squash.tobuffer(channel) for _, player in Players:GetPlayers() do - fire_client(player, buf) + fire_client(player, buf, channel.references) end end end @@ -471,23 +421,43 @@ local function create_undefined(name: string, reliability_type: reliability_type undefined_replicator.write(undefined_packets) end - readers[packet_id] = "dynamic" :: any + readers[packet_id] = dynamic_serdes incoming_queue[packet_id] = create_queue() incoming_signals[packet_id] = construct_signal() - return construct_event(reliability_type, packet_id, dynamic_serialize) + return construct_event(reliability_type, packet_id, dynamic_ser) end -- ENTRYPOINT +local function read(channel: channel, player: Player?) + local packet_id = packet_id_des(channel) + + local reader = readers[packet_id] + local queue = incoming_queue[packet_id] + local signal = incoming_signals[packet_id] + + local data = reader.des(channel) + signal:Fire(data, player) + push(queue, { data, player }) +end + +local function count(x: {}): number + local n = 0 + for _ in x do + n += 1 + end + return n +end + local function extension() if run_context == "server" then local function player_added(player: Player) if outgoing_server[player] then return end - outgoing_server[player] = squash.cursor(fresh_channel_size) + outgoing_server[player] = create_channel() end local function player_removing(player: Player) @@ -502,69 +472,45 @@ local function extension() end local function replicate() - for player, cursor in outgoing_server do - if cursor.Pos <= 0 then + for player, channel in outgoing_server do + if channel.Pos <= 0 then continue end - local buf = squash.tobuffer(cursor) - reliable_remote:FireClient(player, buf) + local buf = squash.tobuffer(channel) + reliable_remote:FireClient( + player, + buf, + if count(channel.references) > 0 then channel.references else nil + ) - outgoing_server[player] = squash.cursor(fresh_channel_size) + outgoing_server[player] = create_channel() end end RunService.Heartbeat:Connect(replicate) - reliable_remote.OnServerEvent:Connect(function(player, buf: buffer) + reliable_remote.OnServerEvent:Connect(function(player, buf: buffer, references: { Instance }?) if typeof(buf) ~= "buffer" then return end - local cursor = squash.frombuffer(buf) - - while cursor.Pos > 0 do - local packet_id = packet_id_des(cursor) + local channel = data_types.channel_from_buffer(buf) + channel.references = if typeof(references) == "table" then references else nil :: any - local reader = readers[packet_id] - local queue = incoming_queue[packet_id] - local signal = incoming_signals[packet_id] - - if typeof(reader) ~= "table" then - local data = dynamic_deserialize(cursor) - signal:Fire(data, player) - push(queue, { data, player }) - continue - end - - local data = reader.des(cursor) - signal:Fire(data, player) - push(queue, { data, player }) + while channel.Pos > 0 do + read(channel, player) end end) - unreliable_remote.OnServerEvent:Connect(function(player, buf: buffer) + unreliable_remote.OnServerEvent:Connect(function(player, buf: buffer, references: { Instance }?) if typeof(buf) ~= "buffer" then return end - local cursor = squash.frombuffer(buf) - local packet_id = packet_id_des(cursor) - - local reader = readers[packet_id] - local queue = incoming_queue[packet_id] - local signal = incoming_signals[packet_id] - - if typeof(reader) ~= "table" then - local data = dynamic_deserialize(cursor) - signal:Fire(data, player) - push(queue, { data, player }) - return - end - - local data = reader.des(cursor) - signal:Fire(data, player) - push(queue, { data, player }) + local channel = data_types.channel_from_buffer(buf) + channel.references = if typeof(references) == "table" then references else nil :: any + read(channel, player) end) else local elapsed = 0 @@ -580,53 +526,28 @@ local function extension() end local buf = squash.tobuffer(outgoing_client) - reliable_remote:FireServer(buf) - outgoing_client = squash.cursor(fresh_channel_size) + reliable_remote:FireServer( + buf, + if count(outgoing_client.references) > 0 then outgoing_client.references else nil + ) + outgoing_client = create_channel() end RunService.Heartbeat:Connect(replicate) - reliable_remote.OnClientEvent:Connect(function(buf: buffer) - local cursor = squash.frombuffer(buf) - - while cursor.Pos > 0 do - local packet_id = packet_id_des(cursor) - - local reader = readers[packet_id] - local queue = incoming_queue[packet_id] - local signal = incoming_signals[packet_id] - - if typeof(reader) ~= "table" then - local data = dynamic_deserialize(cursor) - signal:Fire(data) - push(queue, { data }) - continue - end + reliable_remote.OnClientEvent:Connect(function(buf: buffer, references: { Instance }?) + local channel = data_types.channel_from_buffer(buf) + channel.references = if typeof(references) == "table" then references else nil :: any - local data = reader.des(cursor) - signal:Fire(data) - push(queue, { data }) + while channel.Pos > 0 do + read(channel) end end) - unreliable_remote.OnClientEvent:Connect(function(buf: buffer) - local cursor = squash.frombuffer(buf) - local packet_id = packet_id_des(cursor) - - local reader = readers[packet_id] - local queue = incoming_queue[packet_id] - local signal = incoming_signals[packet_id] - - if typeof(reader) ~= "table" then - local data = dynamic_deserialize(cursor) - signal:Fire(data) - push(queue, { data }) - return - end - - local data = reader.des(cursor) - signal:Fire(data) - push(queue, { data }) + unreliable_remote.OnClientEvent:Connect(function(buf: buffer, references: { Instance }?) + local channel = data_types.channel_from_buffer(buf) + channel.references = if typeof(references) == "table" then references else nil :: any + read(channel) end) end end diff --git a/crates/sapphire-net/lib/types.luau b/crates/sapphire-net/lib/types.luau index 092a76d..d31772f 100644 --- a/crates/sapphire-net/lib/types.luau +++ b/crates/sapphire-net/lib/types.luau @@ -555,6 +555,36 @@ type data_types = { --- Represents a `Vector3int16`. --- @return Vector3int16 Vector3int16: () -> Vector3int16, + --- Represents an `Instance`.\ + --- Instance might be nil if a reference to one doesn't exist. + --- @return Instance? + Instance: () -> Instance?, + --- Represents `unknown`.\ + --- Unknown data is dynamically serialized with buffers. It will take way more space than statically defined data, but it will still be less than just sending it over the network. + --- @return unknown + unknown: () -> unknown, + --- Represents `nil`. + --- @return nil + ["nil"]: () -> nil, + --- Literals are individual values that can be enumerated and distinguished using just `u1`s. This is useful for encoding enums of names, orientations, and other unique identifiers with minimal data. + --- ```luau + --- local literal = Squash.literal("a", 2, "c", true, "e") + --- + --- local cursor = Squash.cursor() + --- literal.ser(cursor, "c") + --- Squash.print(cursor) + --- -- Pos: 1 / 8 + --- -- Buf: { 2 0 0 0 0 0 0 0 } + --- -- ^ + --- local buf = Squash.tobuffer(cursor) + --- + --- local cursor = Squash.frombuffer(buf) + --- print(literal.des(cursor)) + --- -- "c" + --- ``` + --- @param ... T... + --- @return T... + literal: (T...) -> T..., --- # Using Base Conversion --- There are many ways to compress serialized strings, a lossless approach is to treat the string itself as a number and convert the number into a higher base, or radix. This is called [base conversion](https://en.wikipedia.org/wiki/Radix). Strings come in many different *flavors* though, so we need to know how to serialize each *flavor*. Each string is composed of a sequence of certain characters. The set of those certain characters is called that string's smallest **Alphabet**. For example, the string **"Hello, World!"** has the alphabet **"!,HWdelorw"**. We can assign a number to each character in the alphabet like its position in the string. With our example: --- ```luau diff --git a/crates/sapphire-net/wally.lock b/crates/sapphire-net/wally.lock new file mode 100644 index 0000000..7829dd6 --- /dev/null +++ b/crates/sapphire-net/wally.lock @@ -0,0 +1,13 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "data-oriented-house/squash" +version = "2.3.2" +dependencies = [] + +[[package]] +name = "mark-marks/sapphire-net" +version = "0.1.0" +dependencies = [["squash", "data-oriented-house/squash@2.3.2"]] diff --git a/crates/sapphire-net/wally.toml b/crates/sapphire-net/wally.toml index 58c2634..c9e828a 100644 --- a/crates/sapphire-net/wally.toml +++ b/crates/sapphire-net/wally.toml @@ -1,6 +1,6 @@ [package] name = "mark-marks/sapphire-net" -version = "0.1.0" +version = "0.1.1" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" license = "MIT" @@ -15,5 +15,5 @@ include = [ ] [dependencies] -squash = "data-oriented-house/squash@2.3.2" +squash = "data-oriented-house/squash@2.4.0" signal = "red-blox/signal@2.0.2" diff --git a/crates/sapphire/wally.lock b/crates/sapphire/wally.lock new file mode 100644 index 0000000..26cd594 --- /dev/null +++ b/crates/sapphire/wally.lock @@ -0,0 +1,13 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "mark-marks/sapphire" +version = "0.1.1" +dependencies = [["spawn", "red-blox/spawn@1.1.0"]] + +[[package]] +name = "red-blox/spawn" +version = "1.1.0" +dependencies = [] diff --git a/dev/client/controllers/systems/replication.luau b/dev/client/controllers/systems/replication.luau new file mode 100644 index 0000000..ccee6a4 --- /dev/null +++ b/dev/client/controllers/systems/replication.luau @@ -0,0 +1,21 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local components = require(ReplicatedStorage.components) +local events = require(ReplicatedStorage.events) +local sapphire_ecr = require(ReplicatedStorage.Packages.sapphire_ecr) + +local replicator = sapphire_ecr.create_replicator(components.position, components.velocity, components.part) + +local replication_system = {} + +local replication_event = events.data_replication.ecr_registry + +function replication_system.system() + return function() + for _, difference in replication_event.poll() do + replicator.apply_difference(difference) + end + end +end + +return replication_system diff --git a/dev/client/controllers/systems/update_position.luau b/dev/client/controllers/systems/update_position.luau new file mode 100644 index 0000000..5aced98 --- /dev/null +++ b/dev/client/controllers/systems/update_position.luau @@ -0,0 +1,16 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local components = require(ReplicatedStorage.components) +local sapphire_ecr = require(ReplicatedStorage.Packages.sapphire_ecr) + +local update_position = {} + +function update_position.system(registry: sapphire_ecr.Registry) + return function(delta_time: number) + for entity, part: BasePart, position: Vector3 in registry:view(components.part, components.position) do + part.Position = position + end + end +end + +return update_position diff --git a/dev/client/main.client.luau b/dev/client/main.client.luau index a295ce1..376ebb3 100644 --- a/dev/client/main.client.luau +++ b/dev/client/main.client.luau @@ -2,9 +2,14 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") local controllers = script.Parent.controllers local extensions = script.Parent.extensions +require(ReplicatedStorage.components) -- components need to be created before registry creation + local sapphire = require(ReplicatedStorage.Packages.sapphire)() +local sapphire_ecr = require(ReplicatedStorage.Packages.sapphire_ecr) local sapphire_net = require(ReplicatedStorage.Packages.sapphire_net) +local heartbeat_lifecycle = require(extensions.heartbeat_lifecycle) + sapphire.signals.on_extension_registered:Connect(function(extension) print(`Registered extension {extension.identifier}`) end) @@ -21,4 +26,10 @@ sapphire.signals.on_singleton_started:Connect(function(singleton) print(`Started singleton {singleton.identifier}`) end) -sapphire:use(sapphire_net):use(require(extensions.heartbeat_lifecycle)):register_singletons(controllers):start() +sapphire + :use(sapphire_net) + :use(heartbeat_lifecycle) + :use(sapphire_ecr) + :register_singletons(controllers) + :register_singletons(controllers.systems) + :start() diff --git a/dev/server/main.server.luau b/dev/server/main.server.luau index 1be4cf9..e2b330b 100644 --- a/dev/server/main.server.luau +++ b/dev/server/main.server.luau @@ -2,9 +2,14 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") local services = script.Parent.services local extensions = script.Parent.extensions +require(ReplicatedStorage.components) -- components need to be created before registry creation + local sapphire = require(ReplicatedStorage.Packages.sapphire)() +local sapphire_ecr = require(ReplicatedStorage.Packages.sapphire_ecr) local sapphire_net = require(ReplicatedStorage.Packages.sapphire_net) +local heartbeat_lifecycle = require(extensions.heartbeat_lifecycle) + sapphire.signals.on_extension_registered:Connect(function(extension) print(`Registered extension {extension.identifier}`) end) @@ -23,7 +28,9 @@ end) sapphire :use(sapphire_net) - :use(require(extensions.heartbeat_lifecycle)) + :use(heartbeat_lifecycle) + :use(sapphire_ecr) :register_singleton(services.a) :register_singletons(services) + :register_singletons(services.systems) :start() diff --git a/dev/server/services/net_test.luau b/dev/server/services/net_test.luau index c547bd8..6be4b00 100644 --- a/dev/server/services/net_test.luau +++ b/dev/server/services/net_test.luau @@ -1,5 +1,4 @@ --!strict -local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local events = require(ReplicatedStorage.events) diff --git a/dev/server/services/spawn_parts.luau b/dev/server/services/spawn_parts.luau new file mode 100644 index 0000000..ff86626 --- /dev/null +++ b/dev/server/services/spawn_parts.luau @@ -0,0 +1,27 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local components = require(ReplicatedStorage.components) +local sapphire_ecr = require(ReplicatedStorage.Packages.sapphire_ecr) + +local SpawnParts = {} + +function SpawnParts.start() + local spawner = sapphire_ecr.create_spawner(components.part, components.position, components.velocity) + local part_template = Instance.new("Part") + + local max = 2147483647 + + for _ = 1, 1_000 do + local rand = Random.new(math.random(max)) + + local part = part_template:Clone() + part.Anchored = true + part.Parent = workspace + local handle = spawner.spawn_with_handle(part, Vector3.zero, Vector3.zero) + local random_velocity = + Vector3.new(rand:NextNumber(-0.5, 0.5), rand:NextNumber(-0.5, 0.5), rand:NextNumber(-0.5, 0.5)) + handle:set(components.velocity, random_velocity) + end +end + +return SpawnParts diff --git a/dev/server/services/systems/replication.luau b/dev/server/services/systems/replication.luau new file mode 100644 index 0000000..2dad44a --- /dev/null +++ b/dev/server/services/systems/replication.luau @@ -0,0 +1,30 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local components = require(ReplicatedStorage.components) +local events = require(ReplicatedStorage.events) +local sapphire_ecr = require(ReplicatedStorage.Packages.sapphire_ecr) + +local replicator = sapphire_ecr.create_replicator(components.position, components.velocity, components.part) + +local replication_system = {} + +local replication_event = events.data_replication.ecr_registry + +function replication_system.start() + events.test_namespace.loaded.listen(function(_, player) + assert(player, "where player") + replication_event.send_to(player, replicator.get_full_data()) + end) +end + +function replication_system.system() + return function(delta_time: number) + local difference = replicator.calculate_difference() + if not difference then + return + end + replication_event.send_to_all(difference) + end +end + +return replication_system diff --git a/dev/server/services/systems/update_position.luau b/dev/server/services/systems/update_position.luau new file mode 100644 index 0000000..e76a527 --- /dev/null +++ b/dev/server/services/systems/update_position.luau @@ -0,0 +1,18 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local components = require(ReplicatedStorage.components) +local sapphire_ecr = require(ReplicatedStorage.Packages.sapphire_ecr) + +local update_position = {} + +function update_position.system(registry: sapphire_ecr.Registry) + local group = registry:group(components.position, components.velocity) + return function(delta_time: number) + for entity, position: Vector3, velocity: Vector3 in group do + local new_position = position + (velocity * delta_time) + registry:set(entity, components.position, new_position) + end + end +end + +return update_position diff --git a/dev/shared/components.luau b/dev/shared/components.luau new file mode 100644 index 0000000..f7bae1e --- /dev/null +++ b/dev/shared/components.luau @@ -0,0 +1,9 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local ecr = require(ReplicatedStorage.Packages.ecr) + +return { + position = ecr.component() :: Vector3, + velocity = ecr.component() :: Vector3, + part = ecr.component() :: BasePart, +} diff --git a/dev/shared/events.luau b/dev/shared/events.luau index bdd683d..a4dd15b 100644 --- a/dev/shared/events.luau +++ b/dev/shared/events.luau @@ -49,4 +49,12 @@ return { }), } end), + data_replication = sapphire_net.define_namespace("data_replication", function() + return { + ecr_registry = sapphire_net.defined({ + value = t.map(t.uint(4), t.map(t.uint(4), t.unknown())), + reliability_type = "reliable", + }), + } + end), } diff --git a/rokit.toml b/rokit.toml new file mode 100644 index 0000000..6421a2b --- /dev/null +++ b/rokit.toml @@ -0,0 +1,13 @@ +# This file lists tools managed by Rokit, a toolchain manager for Roblox projects. +# For more information, see https://github.com/rojo-rbx/rokit + +# New tools can be added by running `rokit add ` in a terminal. + +[tools] +selene = "kampfkarren/selene@0.27.1" +luau-lsp = "JohnnyMorganz/luau-lsp@1.32.3" +lune = "lune-org/lune@0.8.8" +wally = "upliftgames/wally@0.3.2" +stylua = "johnnymorganz/stylua@0.20.0" +rojo = "rojo-rbx/rojo@7.4.4" +wally-package-types = "johnnymorganz/wally-package-types@1.3.2" diff --git a/selene.toml b/selene.toml index 1b236ba..a1acd99 100644 --- a/selene.toml +++ b/selene.toml @@ -1,17 +1,2 @@ std = "roblox" -exclude = [ - "crates/sapphire/Packages", - "crates/sapphire-data/Packages", - "crates/sapphire-ecr/Packages", - "crates/sapphire-jecs/Packages", - "crates/sapphire-lifecycles/Packages", - "crates/sapphire-logging/Packages", - "crates/sapphire-net/Packages", - "crates/sapphire/node_modules", - "crates/sapphire-data/node_modules", - "crates/sapphire-ecr/node_modules", - "crates/sapphire-jecs/node_modules", - "crates/sapphire-lifecycles/node_modules", - "crates/sapphire-logging/node_modules", - "crates/sapphire-net/node_modules", -] +exclude = ["**/node_modules/**", "**/Packages/**", "**/packages/**"] diff --git a/wally.lock b/wally.lock new file mode 100644 index 0000000..c6ebf7e --- /dev/null +++ b/wally.lock @@ -0,0 +1,48 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "centau/ecr" +version = "0.8.0" +dependencies = [] + +[[package]] +name = "data-oriented-house/squash" +version = "2.4.0" +dependencies = [] + +[[package]] +name = "ffrostflame/keyform" +version = "0.2.2" +dependencies = [["Signal", "ffrostflame/luausignal@0.1.3"], ["TableKit", "ffrostflame/tablekit@0.2.4"]] + +[[package]] +name = "ffrostflame/luausignal" +version = "0.1.3" +dependencies = [] + +[[package]] +name = "ffrostflame/tablekit" +version = "0.2.4" +dependencies = [] + +[[package]] +name = "mark-marks/sapphire-dev" +version = "0.1.0" +dependencies = [["ecr", "centau/ecr@0.8.0"], ["jecs", "ukendio/jecs@0.2.5"], ["keyform", "ffrostflame/keyform@0.2.2"], ["signal", "red-blox/signal@2.0.2"], ["spawn", "red-blox/spawn@1.1.0"], ["squash", "data-oriented-house/squash@2.4.0"]] + +[[package]] +name = "red-blox/signal" +version = "2.0.2" +dependencies = [["Spawn", "red-blox/spawn@1.1.0"]] + +[[package]] +name = "red-blox/spawn" +version = "1.1.0" +dependencies = [] + +[[package]] +name = "ukendio/jecs" +version = "0.2.5" +dependencies = [] diff --git a/wally.toml b/wally.toml index b7ee803..610b089 100644 --- a/wally.toml +++ b/wally.toml @@ -7,7 +7,7 @@ license = "MIT" exclude = ["**"] [dependencies] -squash = "data-oriented-house/squash@2.3.2" +squash = "data-oriented-house/squash@2.4.0" ecr = "centau/ecr@0.8.0" jecs = "ukendio/jecs@0.2.5" signal = "red-blox/signal@2.0.2"