* Fix query types

* Add systems to demo

* Remove comments of inlined versions

* Fix style

* Replication

* Test :iter
This commit is contained in:
Marcus 2024-08-07 18:45:56 +02:00 committed by GitHub
parent 56694ba99f
commit a73eeb1a4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1288 additions and 151 deletions

View file

@ -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"

View file

@ -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"
}
}
}
}

18
demo/.config/blink Normal file
View file

@ -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)
}

View file

@ -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).

337
demo/net/client.luau Normal file
View file

@ -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
},
}

374
demo/net/server.luau Normal file
View file

@ -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,
},
}

View file

@ -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

View file

@ -1,73 +1,8 @@
--!optimize 2
--!native
local jecs = require(game:GetService("ReplicatedStorage").ecs)
type World = jecs.WorldShim
type Entity<T = any> = jecs.Entity<T>
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<T> = { track: (world: World, fn: (changes: {
added: () -> () -> (number, T),
@ -97,7 +32,7 @@ local function diff(a, b)
return false
end
local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
local function ChangeTracker<T>(T: Entity<T>): Tracker<T>
local PreviousT = jecs.pair(jecs.Rest, T)
local add = {}
local added
@ -142,7 +77,7 @@ local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
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<T>(world, T: Entity<T>): Tracker<T>
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

View file

@ -0,0 +1,67 @@
--!nonstrict
--[[
local signal = Signal.new() :: Signal.Signal<string, string>
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<T...> = { [any]: any }
local function collect<T...>(event: Signal<T...>)
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

View file

@ -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<Model>,
Player = world:component(),
Target = world:component(),
Transform = world:component() :: jecs.Entity<CFrame>,
Velocity = world:component(),
}
return table.freeze(components)

View file

@ -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

View file

@ -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: <T>(self: Handle, id: jecs.Entity<T>) -> T?,
add: <T>(self: Handle, id: jecs.Entity<T>) -> Handle,
set: <T>(self: Handle, id: jecs.Entity<T>, 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,
}

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@
print("Hello world, from client!")

View file

@ -1 +0,0 @@