From a73eeb1a4f5cac374da95f4257bc944469bc0f7f Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 7 Aug 2024 18:45:56 +0200 Subject: [PATCH] Demo (#96) * Fix query types * Add systems to demo * Remove comments of inlined versions * Fix style * Replication * Test :iter --- aftman.toml | 1 + demo.project.json | 26 +- demo/.config/blink | 18 + demo/README.md | 14 +- demo/net/client.luau | 337 ++++++++++++++++ demo/net/server.luau | 374 ++++++++++++++++++ demo/src/ReplicatedStorage/std/bt.luau | 40 ++ .../std/changetracker.luau} | 135 +------ demo/src/ReplicatedStorage/std/collect.luau | 67 ++++ .../src/ReplicatedStorage/std/components.luau | 14 + demo/src/ReplicatedStorage/std/ctx.luau | 11 + demo/src/ReplicatedStorage/std/handle.luau | 53 +++ demo/src/ReplicatedStorage/std/init.luau | 20 + demo/src/ReplicatedStorage/std/interval.luau | 19 + demo/src/ReplicatedStorage/std/ref.luau | 18 + demo/src/ReplicatedStorage/std/registry.luau | 31 ++ demo/src/ReplicatedStorage/std/scheduler.luau | 64 +++ demo/src/ReplicatedStorage/std/world.luau | 5 + demo/src/ServerScriptService/main.server.luau | 4 + .../ServerScriptService/systems/mobsMove.luau | 49 +++ .../ServerScriptService/systems/players.luau | 40 ++ .../systems/spawnMobs.luau | 30 ++ .../StarterPlayerScripts/main.client.luau | 6 + .../StarterPlayerScripts/systems/move.luau | 17 + .../systems/syncMobs.luau | 28 ++ .../systems/syncTransforms.luau | 16 + demo/src/client/init.client.luau | 1 - demo/src/server/init.server.luau | 1 - 28 files changed, 1288 insertions(+), 151 deletions(-) create mode 100644 demo/.config/blink create mode 100644 demo/net/client.luau create mode 100644 demo/net/server.luau create mode 100644 demo/src/ReplicatedStorage/std/bt.luau rename demo/src/{shared/common.luau => ReplicatedStorage/std/changetracker.luau} (50%) create mode 100644 demo/src/ReplicatedStorage/std/collect.luau create mode 100644 demo/src/ReplicatedStorage/std/components.luau create mode 100644 demo/src/ReplicatedStorage/std/ctx.luau create mode 100644 demo/src/ReplicatedStorage/std/handle.luau create mode 100644 demo/src/ReplicatedStorage/std/init.luau create mode 100644 demo/src/ReplicatedStorage/std/interval.luau create mode 100644 demo/src/ReplicatedStorage/std/ref.luau create mode 100644 demo/src/ReplicatedStorage/std/registry.luau create mode 100644 demo/src/ReplicatedStorage/std/scheduler.luau create mode 100644 demo/src/ReplicatedStorage/std/world.luau create mode 100644 demo/src/ServerScriptService/main.server.luau create mode 100644 demo/src/ServerScriptService/systems/mobsMove.luau create mode 100644 demo/src/ServerScriptService/systems/players.luau create mode 100644 demo/src/ServerScriptService/systems/spawnMobs.luau create mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau create mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/move.luau create mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/syncMobs.luau create mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/syncTransforms.luau delete mode 100644 demo/src/client/init.client.luau delete mode 100644 demo/src/server/init.server.luau diff --git a/aftman.toml b/aftman.toml index 56cddbd..6810bcc 100644 --- a/aftman.toml +++ b/aftman.toml @@ -4,3 +4,4 @@ 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" +Blink = "1Axen/Blink@0.14.1" diff --git a/demo.project.json b/demo.project.json index 54314ef..80e9432 100644 --- a/demo.project.json +++ b/demo.project.json @@ -3,23 +3,20 @@ "tree": { "$className": "DataModel", "ReplicatedStorage": { - "Shared": { - "$path": "demo/src/shared" - }, + "$className": "ReplicatedStorage", + "$path": "demo/src/ReplicatedStorage", "ecs": { "$path": "src" + }, + "net": { + "$path": "demo/net/client.luau" } }, "ServerScriptService": { - "Server": { - "$path": "demo/src/server" - } - }, - "StarterPlayer": { - "StarterPlayerScripts": { - "Client": { - "$path": "demo/src/client" - } + "$className": "ServerScriptService", + "$path": "demo/src/ServerScriptService", + "net": { + "$path": "demo/net/server.luau" } }, "Workspace": { @@ -66,6 +63,11 @@ "$properties": { "RespectFilteringEnabled": true } + }, + "StarterPlayer": { + "StarterPlayerScripts": { + "$path": "demo/src/StarterPlayer/StarterPlayerScripts" + } } } } diff --git a/demo/.config/blink b/demo/.config/blink new file mode 100644 index 0000000..3675cf7 --- /dev/null +++ b/demo/.config/blink @@ -0,0 +1,18 @@ +option ClientOutput = "../net/client.luau" +option ServerOutput = "../net/server.luau" + +event UpdateTransform { + From: Server, + Type: Unreliable, + Call: SingleSync, + Poll: true, + Data: (f64, CFrame) +} + +event SpawnMob { + From: Server, + Type: Reliable, + Call: SingleSync, + Poll: true, + Data: (f64, CFrame, u8) +} diff --git a/demo/README.md b/demo/README.md index 5223a3e..241cc25 100644 --- a/demo/README.md +++ b/demo/README.md @@ -1,17 +1,15 @@ -# example -Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.4.1. +# Demo -## Getting Started -To build the place from scratch, use: +## Build with Rojo +To build the place, run the following commands from the root of the repository: ```bash -rojo build -o "example.rbxlx" +cd demo +rojo build -o "demo.rbxl" ``` -Next, open `example.rbxlx` in Roblox Studio and start the Rojo server: +Next, open `demo.rbxl` in Roblox Studio and start the Rojo server: ```bash rojo serve ``` - -For more help, check out [the Rojo documentation](https://rojo.space/docs). \ No newline at end of file diff --git a/demo/net/client.luau b/demo/net/client.luau new file mode 100644 index 0000000..29b0f8b --- /dev/null +++ b/demo/net/client.luau @@ -0,0 +1,337 @@ +--!strict +--!native +--!optimize 2 +--!nolint LocalShadow +--#selene: allow(shadowing) +-- File generated by Blink v0.14.1 (https://github.com/1Axen/Blink) +-- This file is not meant to be edited + +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local RunService = game:GetService("RunService") + +if not RunService:IsClient() then + error("Client network module can only be required from the client.") +end + +local Reliable: RemoteEvent = ReplicatedStorage:WaitForChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent +local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:WaitForChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent + +local Invocations = 0 + +local SendSize = 64 +local SendOffset = 0 +local SendCursor = 0 +local SendBuffer = buffer.create(64) +local SendInstances = {} + +local RecieveCursor = 0 +local RecieveBuffer = buffer.create(64) + +local RecieveInstances = {} +local RecieveInstanceCursor = 0 + +type Entry = { + value: any, + next: Entry? +} + +type Queue = { + head: Entry?, + tail: Entry? +} + +type BufferSave = { + Size: number, + Cursor: number, + Buffer: buffer, + Instances: {Instance} +} + +local function Read(Bytes: number) + local Offset = RecieveCursor + RecieveCursor += Bytes + return Offset +end + +local function Save(): BufferSave + return { + Size = SendSize, + Cursor = SendCursor, + Buffer = SendBuffer, + Instances = SendInstances + } +end + +local function Load(Save: BufferSave?) + if Save then + SendSize = Save.Size + SendCursor = Save.Cursor + SendOffset = Save.Cursor + SendBuffer = Save.Buffer + SendInstances = Save.Instances + return + end + + SendSize = 64 + SendCursor = 0 + SendOffset = 0 + SendBuffer = buffer.create(64) + SendInstances = {} +end + +local function Invoke() + if Invocations == 255 then + Invocations = 0 + end + + local Invocation = Invocations + Invocations += 1 + return Invocation +end + +local function Allocate(Bytes: number) + local InUse = (SendCursor + Bytes) + if InUse > SendSize then + --> Avoid resizing the buffer for every write + while InUse > SendSize do + SendSize *= 1.5 + end + + local Buffer = buffer.create(SendSize) + buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) + SendBuffer = Buffer + end + + SendOffset = SendCursor + SendCursor += Bytes + + return SendOffset +end + +local function CreateQueue(): Queue + return { + head = nil, + tail = nil + } +end + +local function Pop(queue: Queue): any + local head = queue.head + if head == nil then + return + end + + queue.head = head.next + return head.value +end + +local function Push(queue: Queue, value: any) + local entry: Entry = { + value = value, + next = nil + } + + if queue.tail ~= nil then + queue.tail.next = entry + end + + queue.tail = entry + + if queue.head == nil then + queue.head = entry + end +end + +local Types = {} +local Calls = table.create(256) + +local Events: any = { + Reliable = table.create(256), + Unreliable = table.create(256) +} + +local Queue: any = { + Reliable = table.create(256), + Unreliable = table.create(256) +} + +Queue.Unreliable[0] = CreateQueue() +Queue.Reliable[0] = CreateQueue() + +function Types.ReadEVENT_UpdateTransform(): (number, CFrame) + -- Read BLOCK: 32 bytes + local BLOCK_START = Read(32) + local Value1 = buffer.readf64(RecieveBuffer, BLOCK_START + 0) + local X = buffer.readf32(RecieveBuffer, BLOCK_START + 8) + local Y = buffer.readf32(RecieveBuffer, BLOCK_START + 12) + local Z = buffer.readf32(RecieveBuffer, BLOCK_START + 16) + local Position = Vector3.new(X, Y, Z) + local rX = buffer.readf32(RecieveBuffer, BLOCK_START + 20) + local rY = buffer.readf32(RecieveBuffer, BLOCK_START + 24) + local rZ = buffer.readf32(RecieveBuffer, BLOCK_START + 28) + local Value2 = CFrame.new(Position) * CFrame.fromOrientation(rX, rY, rZ) + return Value1, Value2 +end + +function Types.WriteEVENT_UpdateTransform(Value1: number, Value2: CFrame): () + -- Allocate BLOCK: 33 bytes + local BLOCK_START = Allocate(33) + buffer.writeu8(SendBuffer, BLOCK_START + 0, 0) + buffer.writef64(SendBuffer, BLOCK_START + 1, Value1) + local Vector = Value2.Position + buffer.writef32(SendBuffer, BLOCK_START + 9, Vector.X) + buffer.writef32(SendBuffer, BLOCK_START + 13, Vector.Y) + buffer.writef32(SendBuffer, BLOCK_START + 17, Vector.Z) + local rX, rY, rZ = Value2:ToOrientation() + buffer.writef32(SendBuffer, BLOCK_START + 21, rX) + buffer.writef32(SendBuffer, BLOCK_START + 25, rY) + buffer.writef32(SendBuffer, BLOCK_START + 29, rZ) +end + +function Types.ReadEVENT_SpawnMob(): (number, CFrame, number) + -- Read BLOCK: 33 bytes + local BLOCK_START = Read(33) + local Value1 = buffer.readf64(RecieveBuffer, BLOCK_START + 0) + local X = buffer.readf32(RecieveBuffer, BLOCK_START + 8) + local Y = buffer.readf32(RecieveBuffer, BLOCK_START + 12) + local Z = buffer.readf32(RecieveBuffer, BLOCK_START + 16) + local Position = Vector3.new(X, Y, Z) + local rX = buffer.readf32(RecieveBuffer, BLOCK_START + 20) + local rY = buffer.readf32(RecieveBuffer, BLOCK_START + 24) + local rZ = buffer.readf32(RecieveBuffer, BLOCK_START + 28) + local Value2 = CFrame.new(Position) * CFrame.fromOrientation(rX, rY, rZ) + local Value3 = buffer.readu8(RecieveBuffer, BLOCK_START + 32) + return Value1, Value2, Value3 +end + +function Types.WriteEVENT_SpawnMob(Value1: number, Value2: CFrame, Value3: number): () + -- Allocate BLOCK: 34 bytes + local BLOCK_START = Allocate(34) + buffer.writeu8(SendBuffer, BLOCK_START + 0, 0) + buffer.writef64(SendBuffer, BLOCK_START + 1, Value1) + local Vector = Value2.Position + buffer.writef32(SendBuffer, BLOCK_START + 9, Vector.X) + buffer.writef32(SendBuffer, BLOCK_START + 13, Vector.Y) + buffer.writef32(SendBuffer, BLOCK_START + 17, Vector.Z) + local rX, rY, rZ = Value2:ToOrientation() + buffer.writef32(SendBuffer, BLOCK_START + 21, rX) + buffer.writef32(SendBuffer, BLOCK_START + 25, rY) + buffer.writef32(SendBuffer, BLOCK_START + 29, rZ) + buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3) +end + + +local function StepReplication() + if SendCursor <= 0 then + return + end + + local Buffer = buffer.create(SendCursor) + buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) + Reliable:FireServer(Buffer, SendInstances) + + SendSize = 64 + SendCursor = 0 + SendOffset = 0 + SendBuffer = buffer.create(64) + table.clear(SendInstances) +end + +local Elapsed = 0 +RunService.Heartbeat:Connect(function(DeltaTime: number) + Elapsed += DeltaTime + if Elapsed >= (1 / 61) then + Elapsed -= (1 / 61) + StepReplication() + end +end) + +Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance}) + RecieveCursor = 0 + RecieveBuffer = Buffer + RecieveInstances = Instances + RecieveInstanceCursor = 0 + local Size = buffer.len(RecieveBuffer) + while (RecieveCursor < Size) do + -- Read BLOCK: 1 bytes + local BLOCK_START = Read(1) + local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0) + if Index == 0 then + Push(Queue.Reliable[0], table.pack(Types.ReadEVENT_SpawnMob())) + end + end +end) + +Unreliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance}) + RecieveCursor = 0 + RecieveBuffer = Buffer + RecieveInstances = Instances + RecieveInstanceCursor = 0 + local Size = buffer.len(RecieveBuffer) + while (RecieveCursor < Size) do + -- Read BLOCK: 1 bytes + local BLOCK_START = Read(1) + local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0) + if Index == 0 then + Push(Queue.Unreliable[0], table.pack(Types.ReadEVENT_UpdateTransform())) + end + end +end) + +return { + StepReplication = StepReplication, + + UpdateTransform = { + Iter = function(): () -> (number, number, CFrame) + local index = 0 + local queue = Queue.Unreliable[0] + return function (): (number, number, CFrame) + index += 1 + local arguments = Pop(queue) + if arguments ~= nil then + return index, unpack(arguments, 1, arguments.n) + end + return + end + end, + Next = function(): () -> (number, number, CFrame) + local index = 0 + local queue = Queue.Unreliable[0] + return function (): (number, number, CFrame) + index += 1 + local arguments = Pop(queue) + if arguments ~= nil then + return index, unpack(arguments, 1, arguments.n) + end + return + end + end + }, + SpawnMob = { + Iter = function(): () -> (number, number, CFrame, number) + local index = 0 + local queue = Queue.Reliable[0] + return function (): (number, number, CFrame, number) + index += 1 + local arguments = Pop(queue) + if arguments ~= nil then + return index, unpack(arguments, 1, arguments.n) + end + return + end + end, + Next = function(): () -> (number, number, CFrame, number) + local index = 0 + local queue = Queue.Reliable[0] + return function (): (number, number, CFrame, number) + index += 1 + local arguments = Pop(queue) + if arguments ~= nil then + return index, unpack(arguments, 1, arguments.n) + end + return + end + end + }, + +} \ No newline at end of file diff --git a/demo/net/server.luau b/demo/net/server.luau new file mode 100644 index 0000000..a31208b --- /dev/null +++ b/demo/net/server.luau @@ -0,0 +1,374 @@ +--!strict +--!native +--!optimize 2 +--!nolint LocalShadow +--#selene: allow(shadowing) +-- File generated by Blink v0.14.1 (https://github.com/1Axen/Blink) +-- This file is not meant to be edited + +local Players = game:GetService("Players") +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local RunService = game:GetService("RunService") + +if not RunService:IsServer() then + error("Server network module can only be required from the server.") +end + +local Reliable: RemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent +if not Reliable then + local RemoteEvent = Instance.new("RemoteEvent") + RemoteEvent.Name = "BLINK_RELIABLE_REMOTE" + RemoteEvent.Parent = ReplicatedStorage + Reliable = RemoteEvent +end + +local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent +if not Unreliable then + local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent") + UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE" + UnreliableRemoteEvent.Parent = ReplicatedStorage + Unreliable = UnreliableRemoteEvent +end + +local Invocations = 0 + +local SendSize = 64 +local SendOffset = 0 +local SendCursor = 0 +local SendBuffer = buffer.create(64) +local SendInstances = {} + +local RecieveCursor = 0 +local RecieveBuffer = buffer.create(64) + +local RecieveInstances = {} +local RecieveInstanceCursor = 0 + +type Entry = { + value: any, + next: Entry? +} + +type Queue = { + head: Entry?, + tail: Entry? +} + +type BufferSave = { + Size: number, + Cursor: number, + Buffer: buffer, + Instances: {Instance} +} + +local function Read(Bytes: number) + local Offset = RecieveCursor + RecieveCursor += Bytes + return Offset +end + +local function Save(): BufferSave + return { + Size = SendSize, + Cursor = SendCursor, + Buffer = SendBuffer, + Instances = SendInstances + } +end + +local function Load(Save: BufferSave?) + if Save then + SendSize = Save.Size + SendCursor = Save.Cursor + SendOffset = Save.Cursor + SendBuffer = Save.Buffer + SendInstances = Save.Instances + return + end + + SendSize = 64 + SendCursor = 0 + SendOffset = 0 + SendBuffer = buffer.create(64) + SendInstances = {} +end + +local function Invoke() + if Invocations == 255 then + Invocations = 0 + end + + local Invocation = Invocations + Invocations += 1 + return Invocation +end + +local function Allocate(Bytes: number) + local InUse = (SendCursor + Bytes) + if InUse > SendSize then + --> Avoid resizing the buffer for every write + while InUse > SendSize do + SendSize *= 1.5 + end + + local Buffer = buffer.create(SendSize) + buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) + SendBuffer = Buffer + end + + SendOffset = SendCursor + SendCursor += Bytes + + return SendOffset +end + +local function CreateQueue(): Queue + return { + head = nil, + tail = nil + } +end + +local function Pop(queue: Queue): any + local head = queue.head + if head == nil then + return + end + + queue.head = head.next + return head.value +end + +local function Push(queue: Queue, value: any) + local entry: Entry = { + value = value, + next = nil + } + + if queue.tail ~= nil then + queue.tail.next = entry + end + + queue.tail = entry + + if queue.head == nil then + queue.head = entry + end +end + +local Types = {} +local Calls = table.create(256) + +local Events: any = { + Reliable = table.create(256), + Unreliable = table.create(256) +} + +local Queue: any = { + Reliable = table.create(256), + Unreliable = table.create(256) +} + + +function Types.ReadEVENT_UpdateTransform(): (number, CFrame) + -- Read BLOCK: 32 bytes + local BLOCK_START = Read(32) + local Value1 = buffer.readf64(RecieveBuffer, BLOCK_START + 0) + local X = buffer.readf32(RecieveBuffer, BLOCK_START + 8) + local Y = buffer.readf32(RecieveBuffer, BLOCK_START + 12) + local Z = buffer.readf32(RecieveBuffer, BLOCK_START + 16) + local Position = Vector3.new(X, Y, Z) + local rX = buffer.readf32(RecieveBuffer, BLOCK_START + 20) + local rY = buffer.readf32(RecieveBuffer, BLOCK_START + 24) + local rZ = buffer.readf32(RecieveBuffer, BLOCK_START + 28) + local Value2 = CFrame.new(Position) * CFrame.fromOrientation(rX, rY, rZ) + return Value1, Value2 +end + +function Types.WriteEVENT_UpdateTransform(Value1: number, Value2: CFrame): () + -- Allocate BLOCK: 33 bytes + local BLOCK_START = Allocate(33) + buffer.writeu8(SendBuffer, BLOCK_START + 0, 0) + buffer.writef64(SendBuffer, BLOCK_START + 1, Value1) + local Vector = Value2.Position + buffer.writef32(SendBuffer, BLOCK_START + 9, Vector.X) + buffer.writef32(SendBuffer, BLOCK_START + 13, Vector.Y) + buffer.writef32(SendBuffer, BLOCK_START + 17, Vector.Z) + local rX, rY, rZ = Value2:ToOrientation() + buffer.writef32(SendBuffer, BLOCK_START + 21, rX) + buffer.writef32(SendBuffer, BLOCK_START + 25, rY) + buffer.writef32(SendBuffer, BLOCK_START + 29, rZ) +end + +function Types.ReadEVENT_SpawnMob(): (number, CFrame, number) + -- Read BLOCK: 33 bytes + local BLOCK_START = Read(33) + local Value1 = buffer.readf64(RecieveBuffer, BLOCK_START + 0) + local X = buffer.readf32(RecieveBuffer, BLOCK_START + 8) + local Y = buffer.readf32(RecieveBuffer, BLOCK_START + 12) + local Z = buffer.readf32(RecieveBuffer, BLOCK_START + 16) + local Position = Vector3.new(X, Y, Z) + local rX = buffer.readf32(RecieveBuffer, BLOCK_START + 20) + local rY = buffer.readf32(RecieveBuffer, BLOCK_START + 24) + local rZ = buffer.readf32(RecieveBuffer, BLOCK_START + 28) + local Value2 = CFrame.new(Position) * CFrame.fromOrientation(rX, rY, rZ) + local Value3 = buffer.readu8(RecieveBuffer, BLOCK_START + 32) + return Value1, Value2, Value3 +end + +function Types.WriteEVENT_SpawnMob(Value1: number, Value2: CFrame, Value3: number): () + -- Allocate BLOCK: 34 bytes + local BLOCK_START = Allocate(34) + buffer.writeu8(SendBuffer, BLOCK_START + 0, 0) + buffer.writef64(SendBuffer, BLOCK_START + 1, Value1) + local Vector = Value2.Position + buffer.writef32(SendBuffer, BLOCK_START + 9, Vector.X) + buffer.writef32(SendBuffer, BLOCK_START + 13, Vector.Y) + buffer.writef32(SendBuffer, BLOCK_START + 17, Vector.Z) + local rX, rY, rZ = Value2:ToOrientation() + buffer.writef32(SendBuffer, BLOCK_START + 21, rX) + buffer.writef32(SendBuffer, BLOCK_START + 25, rY) + buffer.writef32(SendBuffer, BLOCK_START + 29, rZ) + buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3) +end + + +local PlayersMap: {[Player]: BufferSave} = {} + +Players.PlayerRemoving:Connect(function(Player) + PlayersMap[Player] = nil +end) + +local function StepReplication() + for Player, Send in PlayersMap do + if Send.Cursor <= 0 then + continue + end + + local Buffer = buffer.create(Send.Cursor) + buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor) + Reliable:FireClient(Player, Buffer, Send.Instances) + + Send.Size = 64 + Send.Cursor = 0 + Send.Buffer = buffer.create(64) + table.clear(Send.Instances) + end +end + +RunService.Heartbeat:Connect(StepReplication) + +Reliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: {Instance}) + RecieveCursor = 0 + RecieveBuffer = Buffer + RecieveInstances = Instances + RecieveInstanceCursor = 0 + local Size = buffer.len(RecieveBuffer) + while (RecieveCursor < Size) do + -- Read BLOCK: 1 bytes + local BLOCK_START = Read(1) + local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0) + end +end) + +Unreliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: {Instance}) + RecieveCursor = 0 + RecieveBuffer = Buffer + RecieveInstances = Instances + RecieveInstanceCursor = 0 + local Size = buffer.len(RecieveBuffer) + while (RecieveCursor < Size) do + -- Read BLOCK: 1 bytes + local BLOCK_START = Read(1) + local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0) + end +end) + +return { + StepReplication = StepReplication, + + UpdateTransform = { + Fire = function(Player: Player, Value1: number, Value2: CFrame): () + Load() + Types.WriteEVENT_UpdateTransform(Value1, Value2) + local Buffer = buffer.create(SendCursor) + buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) + Unreliable:FireClient(Player, Buffer, SendInstances) + end, + FireAll = function(Value1: number, Value2: CFrame): () + Load() + Types.WriteEVENT_UpdateTransform(Value1, Value2) + local Buffer = buffer.create(SendCursor) + buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) + Unreliable:FireAllClients(Buffer, SendInstances) + end, + FireList = function(List: {Player}, Value1: number, Value2: CFrame): () + Load() + Types.WriteEVENT_UpdateTransform(Value1, Value2) + local Buffer = buffer.create(SendCursor) + buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) + for _, Player in List do + Unreliable:FireClient(Player, Buffer, SendInstances) + end + end, + FireExcept = function(Except: Player, Value1: number, Value2: CFrame): () + Load() + Types.WriteEVENT_UpdateTransform(Value1, Value2) + local Buffer = buffer.create(SendCursor) + buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor) + for _, Player in Players:GetPlayers() do + if Player == Except then + continue + end + Unreliable:FireClient(Player, Buffer, SendInstances) + end + end, + }, + SpawnMob = { + Fire = function(Player: Player, Value1: number, Value2: CFrame, Value3: number): () + Load(PlayersMap[Player]) + Types.WriteEVENT_SpawnMob(Value1, Value2, Value3) + PlayersMap[Player] = Save() + end, + FireAll = function(Value1: number, Value2: CFrame, Value3: number): () + Load() + Types.WriteEVENT_SpawnMob(Value1, Value2, Value3) + local Buffer, Size, Instances = SendBuffer, SendCursor, SendInstances + for _, Player in Players:GetPlayers() do + Load(PlayersMap[Player]) + local Position = Allocate(Size) + buffer.copy(SendBuffer, Position, Buffer, 0, Size) + table.move(Instances, 1, #Instances, #SendInstances + 1, SendInstances) + PlayersMap[Player] = Save() + end + end, + FireList = function(List: {Player}, Value1: number, Value2: CFrame, Value3: number): () + Load() + Types.WriteEVENT_SpawnMob(Value1, Value2, Value3) + local Buffer, Size, Instances = SendBuffer, SendCursor, SendInstances + for _, Player in List do + Load(PlayersMap[Player]) + local Position = Allocate(Size) + buffer.copy(SendBuffer, Position, Buffer, 0, Size) + table.move(Instances, 1, #Instances, #SendInstances + 1, SendInstances) + PlayersMap[Player] = Save() + end + end, + FireExcept = function(Except: Player, Value1: number, Value2: CFrame, Value3: number): () + Load() + Types.WriteEVENT_SpawnMob(Value1, Value2, Value3) + local Buffer, Size, Instances = SendBuffer, SendCursor, SendInstances + for _, Player in Players:GetPlayers() do + if Player == Except then + continue + end + Load(PlayersMap[Player]) + local Position = Allocate(Size) + buffer.copy(SendBuffer, Position, Buffer, 0, Size) + table.move(Instances, 1, #Instances, #SendInstances + 1, SendInstances) + PlayersMap[Player] = Save() + end + end, + }, + +} \ No newline at end of file diff --git a/demo/src/ReplicatedStorage/std/bt.luau b/demo/src/ReplicatedStorage/std/bt.luau new file mode 100644 index 0000000..9cb6d1c --- /dev/null +++ b/demo/src/ReplicatedStorage/std/bt.luau @@ -0,0 +1,40 @@ +--!optimize 2 +--!native + +-- original author @centau + +local SUCCESS = 0 +local FAILURE = 1 +local RUNNING = 2 + +local function SEQUENCE(nodes) + return function(...) + for _, node in nodes do + local status = node(...) + if status == FAILURE or status == RUNNING then + return status + end + end + return SUCCESS + end +end + +local function FALLBACK(nodes) + return function(...) + for _, node in nodes do + local status = node(...) + if status == SUCCESS or status == RUNNING then + return status + end + end + return FAILURE + end +end + +local bt = { + SEQUENCE = SEQUENCE, + FALLBACK = FALLBACK, + RUNNING = RUNNING +} + +return bt diff --git a/demo/src/shared/common.luau b/demo/src/ReplicatedStorage/std/changetracker.luau similarity index 50% rename from demo/src/shared/common.luau rename to demo/src/ReplicatedStorage/std/changetracker.luau index 38b1ff3..bd5051a 100644 --- a/demo/src/shared/common.luau +++ b/demo/src/ReplicatedStorage/std/changetracker.luau @@ -1,73 +1,8 @@ ---!optimize 2 ---!native - local jecs = require(game:GetService("ReplicatedStorage").ecs) -type World = jecs.WorldShim -type Entity = jecs.Entity - -local function panic(str) - -- We don't want to interrupt the loop when we error - task.spawn(error, str) -end - -local function Scheduler(world, ...) - local systems = { ... } - local systemsNames = {} - local N = #systems - local system - local dt - - for i, module in systems do - local sys = require(module) - systems[i] = sys - local file, line = debug.info(2, "sl") - systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}` - end - - local function run() - local name = systemsNames[system] - - debug.profilebegin(name) - debug.setmemorycategory(name) - system(world, dt) - debug.profileend() - end - - local function loop(sinceLastFrame) - debug.profilebegin("loop()") - - for i = N, 1, -1 do - system = systems[i] - - dt = sinceLastFrame - - local didNotYield, why = xpcall(function() - for _ in run do end - end, debug.traceback) - - if didNotYield then - continue - end - - if string.find(why, "thread is not yieldable") then - N -= 1 - local name = table.remove(systems, i) - panic("Not allowed to yield in the systems." - .. "\n" - .. `System: {name} has been ejected` - ) - else - panic(why) - end - end - - debug.profileend() - debug.resetmemorycategory() - end - - return loop -end +local world = require(script.Parent.world) +local sparse = ((world :: any) :: jecs.World).entityIndex.sparse +type World = world.World type Tracker = { track: (world: World, fn: (changes: { added: () -> () -> (number, T), @@ -97,7 +32,7 @@ local function diff(a, b) return false end -local function ChangeTracker(world, T: Entity): Tracker +local function ChangeTracker(T: Entity): Tracker local PreviousT = jecs.pair(jecs.Rest, T) local add = {} local added @@ -142,7 +77,7 @@ local function ChangeTracker(world, T: Entity): Tracker id, new, old = q.next() end - local record = world.entityIndex.sparse[id] + local record = sparse[id] local archetype = record.archetype local column = archetype.records[PreviousT].column local data = if is_trivial then new else table.clone(new) @@ -197,62 +132,4 @@ local function ChangeTracker(world, T: Entity): Tracker return tracker end -local bt -do - local SUCCESS = 0 - local FAILURE = 1 - local RUNNING = 2 - - local function SEQUENCE(nodes) - return function(...) - for _, node in nodes do - local status = node(...) - if status == FAILURE or status == RUNNING then - return status - end - end - return SUCCESS - end - end - local function FALLBACK(nodes) - return function(...) - for _, node in nodes do - local status = node(...) - if status == SUCCESS or status == RUNNING then - return status - end - end - return FAILURE - end - end - bt = { - SEQUENCE = SEQUENCE, - FALLBACK = FALLBACK, - RUNNING = RUNNING - } -end - -local function interval(s) - local pin - - local function throttle() - if not pin then - pin = os.clock() - end - - local elapsed = os.clock() - pin > s - if elapsed then - pin = os.clock() - end - - return elapsed - end - return throttle -end - -return { - Scheduler = Scheduler, - ChangeTracker = ChangeTracker, - interval = interval, - BehaviorTree = bt -} +return ChangeTracker diff --git a/demo/src/ReplicatedStorage/std/collect.luau b/demo/src/ReplicatedStorage/std/collect.luau new file mode 100644 index 0000000..edd9870 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/collect.luau @@ -0,0 +1,67 @@ +--!nonstrict + +--[[ + local signal = Signal.new() :: Signal.Signal + local events = collect(signal) + local function system(world) + for id, str1, str2 in events do + -- + end + end +]] + +--[[ +original author by @memorycode + +MIT License + +Copyright (c) 2024 Michael + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--]] + +type Signal = { [any]: any } +local function collect(event: Signal) + local storage = {} + local mt = {} + local iter = function() + local n = #storage + return function() + if n <= 0 then + mt.__iter = nil + return nil + end + + n -= 1 + return n + 1, unpack(table.remove(storage, 1) :: any) + end + end + + local disconnect = event:Connect(function(...) + table.insert(storage, { ... }) + mt.__iter = iter + end) + + setmetatable(storage, mt) + return (storage :: any) :: () -> (number, T...), function() + disconnect() + end +end + +return collect diff --git a/demo/src/ReplicatedStorage/std/components.luau b/demo/src/ReplicatedStorage/std/components.luau new file mode 100644 index 0000000..70b7425 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/components.luau @@ -0,0 +1,14 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +local world = require(script.Parent.world) + +local components = { + Character = world:component(), + Mob = world:component(), + Model = world:component() :: jecs.Entity, + Player = world:component(), + Target = world:component(), + Transform = world:component() :: jecs.Entity, + Velocity = world:component(), +} + +return table.freeze(components) diff --git a/demo/src/ReplicatedStorage/std/ctx.luau b/demo/src/ReplicatedStorage/std/ctx.luau new file mode 100644 index 0000000..9a4cbf8 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/ctx.luau @@ -0,0 +1,11 @@ +local world = require(script.Parent.world) +local handle = require(script.Parent.handle) + +local singleton = world:entity() + +local function ctx() + -- Cannot cache handles because they will get invalidated + return handle(singleton) +end + +return ctx diff --git a/demo/src/ReplicatedStorage/std/handle.luau b/demo/src/ReplicatedStorage/std/handle.luau new file mode 100644 index 0000000..785ae3a --- /dev/null +++ b/demo/src/ReplicatedStorage/std/handle.luau @@ -0,0 +1,53 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +local world = require(script.Parent.world) + +type Handle = { + has: (self: Handle, id: jecs.Entity) -> boolean, + get: (self: Handle, id: jecs.Entity) -> T?, + add: (self: Handle, id: jecs.Entity) -> Handle, + set: (self: Handle, id: jecs.Entity, value: T) -> Handle, + id: (self: Handle?) -> jecs.Entity +} + +local handle: (e: jecs.Entity) -> Handle + +do + local e + local function has(_, id) + return world:has(e, id) + end + local function get(_, id) + return world:get(e, id) + end + local function set(self, id, value) + world:set(e, id, value) + return self + end + local function add(self, id) + world:add(e, id) + return self + end + local function clear(self) + world:clear(e) + return self + end + local function id() + return e + end + + local entity = { + has = has, + get = get, + set = set, + add = add, + clear = clear, + id = id, + } + + function handle(id) + e = id + return entity + end +end + +return handle diff --git a/demo/src/ReplicatedStorage/std/init.luau b/demo/src/ReplicatedStorage/std/init.luau new file mode 100644 index 0000000..23f6cc3 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/init.luau @@ -0,0 +1,20 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +local world = require(script.world) +export type World = world.World + +local std = { + ChangeTracker = require(script.changetracker), + Scheduler = require(script.scheduler), + bt = require(script.bt), + collect = require(script.collect), + components = require(script.components), + ctx = require(script.ctx), + handle = require(script.handle), + interval = require(script.interval), + ref = require(script.ref), + world = world, + pair = jecs.pair, + __ = jecs.w, +} + +return std diff --git a/demo/src/ReplicatedStorage/std/interval.luau b/demo/src/ReplicatedStorage/std/interval.luau new file mode 100644 index 0000000..fadae90 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/interval.luau @@ -0,0 +1,19 @@ +local function interval(s) + local pin + + local function throttle() + if not pin then + pin = os.clock() + end + + local elapsed = os.clock() - pin > s + if elapsed then + pin = os.clock() + end + + return elapsed + end + return throttle +end + +return interval diff --git a/demo/src/ReplicatedStorage/std/ref.luau b/demo/src/ReplicatedStorage/std/ref.luau new file mode 100644 index 0000000..25b61b3 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/ref.luau @@ -0,0 +1,18 @@ +local world = require(script.Parent.world) +local handle = require(script.Parent.handle) +local refs = {} + +local function ref(key) + if not key then + return handle(world:entity()) + end + local e = refs[key] + if not e then + e = world:entity() + refs[key] = e + end + -- Cannot cache handles because they will get invalidated + return handle(e) +end + +return ref diff --git a/demo/src/ReplicatedStorage/std/registry.luau b/demo/src/ReplicatedStorage/std/registry.luau new file mode 100644 index 0000000..04b55cc --- /dev/null +++ b/demo/src/ReplicatedStorage/std/registry.luau @@ -0,0 +1,31 @@ +local reserved = 0 + +local function reserve() + reserved += 1 + return reserved +end + +-- If you don't like passing around a world singleton +-- and you need to register component IDs, just register them. +-- I dont use this because I like adding component traits +--[[ + local components = { + Model = registry.reserve(), + Transform = registry.reserve(), + } + + local world = registry.register(jecs.World.new()) + local e = world:entity() + world:set(e, components.Transform, CFrame) +]] +local function register(world) + for _ = 1, reserved do + world:component() + end + return world +end + +return { + reserve = reserve, + register = register, +} diff --git a/demo/src/ReplicatedStorage/std/scheduler.luau b/demo/src/ReplicatedStorage/std/scheduler.luau new file mode 100644 index 0000000..c0aac28 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/scheduler.luau @@ -0,0 +1,64 @@ +local function panic(str) + -- We don't want to interrupt the loop when we error + task.spawn(error, str) +end + +local function Scheduler(...) + local systems = { ... } + local systemsNames = {} + local N = #systems + local system + local dt + + for i, module in systems do + local sys = require(module) + systems[i] = sys + local file, line = debug.info(2, "sl") + systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}` + end + + local function run() + local name = systemsNames[system] + + debug.profilebegin(name) + debug.setmemorycategory(name) + system(dt) + debug.profileend() + end + + local function loop(sinceLastFrame) + debug.profilebegin("loop()") + + for i = N, 1, -1 do + system = systems[i] + + dt = sinceLastFrame + + local didNotYield, why = xpcall(function() + for _ in run do end + end, debug.traceback) + + if didNotYield then + continue + end + + if string.find(why, "thread is not yieldable") then + N -= 1 + local name = table.remove(systems, i) + panic("Not allowed to yield in the systems." + .. "\n" + .. `System: {name} has been ejected` + ) + else + panic(why) + end + end + + debug.profileend() + debug.resetmemorycategory() + end + + return loop +end + +return Scheduler diff --git a/demo/src/ReplicatedStorage/std/world.luau b/demo/src/ReplicatedStorage/std/world.luau new file mode 100644 index 0000000..84b90b3 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/world.luau @@ -0,0 +1,5 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +export type World = jecs.WorldShim + +-- I like the idea of only having the world be a singleton. +return jecs.World.new() diff --git a/demo/src/ServerScriptService/main.server.luau b/demo/src/ServerScriptService/main.server.luau new file mode 100644 index 0000000..6a521b2 --- /dev/null +++ b/demo/src/ServerScriptService/main.server.luau @@ -0,0 +1,4 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local std = require(ReplicatedStorage.std) +local loop = std.Scheduler(unpack(script.Parent.systems:GetChildren())) +game:GetService("RunService").Heartbeat:Connect(loop) diff --git a/demo/src/ServerScriptService/systems/mobsMove.luau b/demo/src/ServerScriptService/systems/mobsMove.luau new file mode 100644 index 0000000..6cf10f0 --- /dev/null +++ b/demo/src/ServerScriptService/systems/mobsMove.luau @@ -0,0 +1,49 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local blink = require(game:GetService("ServerScriptService").net) +local jecs = require(ReplicatedStorage.ecs) +local pair = jecs.pair +local __ = jecs.Wildcard + +local std = require(ReplicatedStorage.std) +local world = std.world + +local cts = std.components + +local Mob = cts.Mob +local Transform = cts.Transform +local Velocity = cts.Velocity +local Player = cts.Player +local Character = cts.Character +print("client Model", cts.Model) + +local function mobsMove(dt: number) + local players = world:query(Character):with(Player) + + for mob, cf, v in world:query(Transform, Velocity):with(Mob):iter() do + local p = cf.Position + + local target + for playerId, character in players do + local pos = character.PrimaryPart.Position + if true then + target = pos + break + end + if not target then + target = pos + elseif (p - pos).Magnitude < (p - target) then + target = pos + end + end + + if not target then + continue + end + + local moving = CFrame.new(p + (target - p).Unit * dt * v) + world:set(mob, Transform, moving) + blink.UpdateTransform.FireAll(mob, moving) + end +end + +return mobsMove diff --git a/demo/src/ServerScriptService/systems/players.luau b/demo/src/ServerScriptService/systems/players.luau new file mode 100644 index 0000000..8a68573 --- /dev/null +++ b/demo/src/ServerScriptService/systems/players.luau @@ -0,0 +1,40 @@ +local Players = game:GetService("Players") +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local std = require(ReplicatedStorage.std) +local ref = std.ref +local collect = std.collect + +local cts = std.components +local Player = cts.Player +local Character = cts.Character + +local playersAdded = collect(Players.PlayerAdded) +local playersRemoved = collect(Players.PlayerRemoving) + +local conn = {} + +local function players() + for _, player in playersAdded do + std.world:set( + std.world:entity(), + std.world:entity()) + + local e = ref(player.UserId):set(Player, player) + local characterAdd = player.CharacterAdded + conn[e.id()] = characterAdd:Connect(function(rig) + while rig.Parent ~= workspace do + task.wait() + end + e:set(Character, rig) + end) + end + + for _, player in playersRemoved do + local id = ref(player.UserId):clear().id() + conn[id]:Disconnect() + conn[id] = nil + end +end + +return players diff --git a/demo/src/ServerScriptService/systems/spawnMobs.luau b/demo/src/ServerScriptService/systems/spawnMobs.luau new file mode 100644 index 0000000..748a03d --- /dev/null +++ b/demo/src/ServerScriptService/systems/spawnMobs.luau @@ -0,0 +1,30 @@ +local std = require(game:GetService("ReplicatedStorage").std) +local blink = require(game:GetService("ServerScriptService").net) + +local ref = std.ref +local interval = std.interval +local cts = std.components + +local Mob = cts.Mob +local Transform = cts.Transform +local Velocity = cts.Velocity + +local throttle = interval(5) + +local function spawnMobs() + if throttle() then + local p = Vector3.new(0, 5, 0) + local cf = CFrame.new(p) + local v = 5 + + local id = ref() + :set(Velocity, v) + :set(Transform, cf) + :add(Mob) + .id() + + blink.SpawnMob.FireAll(id, cf, v) + end +end + +return spawnMobs diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau b/demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau new file mode 100644 index 0000000..1d7b555 --- /dev/null +++ b/demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau @@ -0,0 +1,6 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local std = require(ReplicatedStorage.std) + +print(script.Parent:WaitForChild("systems"):GetChildren()) +local loop = std.Scheduler(unpack(script.Parent:WaitForChild("systems"):GetChildren())) +game:GetService("RunService").Heartbeat:Connect(loop) diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/move.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/move.luau new file mode 100644 index 0000000..a3270b9 --- /dev/null +++ b/demo/src/StarterPlayer/StarterPlayerScripts/systems/move.luau @@ -0,0 +1,17 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local std = require(ReplicatedStorage.std) +local world = std.world + +local cts = std.components + +local Model = cts.Model +local Transform = cts.Transform + +local function move(dt: number) + for _, cf, model in world:query(Transform, Model) do + model.PrimaryPart.CFrame = cf + end +end + +return move diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncMobs.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncMobs.luau new file mode 100644 index 0000000..0c6e89f --- /dev/null +++ b/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncMobs.luau @@ -0,0 +1,28 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local blink = require(ReplicatedStorage.net) +local std = require(ReplicatedStorage.std) +local ref = std.ref +local world = std.world +local cts = std.components + +local function syncMobs() + for _, id, cf, vel in blink.SpawnMob.Iter() do + local part = Instance.new("Part") + part.Size = Vector3.one * 5 + part.BrickColor = BrickColor.Red() + part.Anchored = true + local model = Instance.new("Model") + model.PrimaryPart = part + part.Parent = model + model.Parent = workspace + + ref("server-"..id) + :set(cts.Transform, cf) + :set(cts.Velocity, vel) + :set(cts.Model, model) + :add(cts.Mob) + end + +end + +return syncMobs diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncTransforms.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncTransforms.luau new file mode 100644 index 0000000..6fa3064 --- /dev/null +++ b/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncTransforms.luau @@ -0,0 +1,16 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local blink = require(ReplicatedStorage.net) +local std = require(ReplicatedStorage.std) +local ref = std.ref +local world = std.world + +local cts = std.components + +local function syncTransforms() + for _, id, cf in blink.UpdateTransform.Iter() do + ref("server-"..id) + :set(cts.Transform, cf) + end +end + +return syncTransforms diff --git a/demo/src/client/init.client.luau b/demo/src/client/init.client.luau deleted file mode 100644 index 505f71c..0000000 --- a/demo/src/client/init.client.luau +++ /dev/null @@ -1 +0,0 @@ -print("Hello world, from client!") \ No newline at end of file diff --git a/demo/src/server/init.server.luau b/demo/src/server/init.server.luau deleted file mode 100644 index 8b13789..0000000 --- a/demo/src/server/init.server.luau +++ /dev/null @@ -1 +0,0 @@ -