mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
Demo (#96)
* Fix query types * Add systems to demo * Remove comments of inlined versions * Fix style * Replication * Test :iter
This commit is contained in:
parent
56694ba99f
commit
a73eeb1a4f
28 changed files with 1288 additions and 151 deletions
|
@ -4,3 +4,4 @@ rojo = "rojo-rbx/rojo@7.4.1"
|
||||||
stylua = "johnnymorganz/stylua@0.19.1"
|
stylua = "johnnymorganz/stylua@0.19.1"
|
||||||
selene = "kampfkarren/selene@0.26.1"
|
selene = "kampfkarren/selene@0.26.1"
|
||||||
wally-patch-package = "Barocena/wally-patch-package@1.2.1"
|
wally-patch-package = "Barocena/wally-patch-package@1.2.1"
|
||||||
|
Blink = "1Axen/Blink@0.14.1"
|
||||||
|
|
|
@ -3,23 +3,20 @@
|
||||||
"tree": {
|
"tree": {
|
||||||
"$className": "DataModel",
|
"$className": "DataModel",
|
||||||
"ReplicatedStorage": {
|
"ReplicatedStorage": {
|
||||||
"Shared": {
|
"$className": "ReplicatedStorage",
|
||||||
"$path": "demo/src/shared"
|
"$path": "demo/src/ReplicatedStorage",
|
||||||
},
|
|
||||||
"ecs": {
|
"ecs": {
|
||||||
"$path": "src"
|
"$path": "src"
|
||||||
|
},
|
||||||
|
"net": {
|
||||||
|
"$path": "demo/net/client.luau"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ServerScriptService": {
|
"ServerScriptService": {
|
||||||
"Server": {
|
"$className": "ServerScriptService",
|
||||||
"$path": "demo/src/server"
|
"$path": "demo/src/ServerScriptService",
|
||||||
}
|
"net": {
|
||||||
},
|
"$path": "demo/net/server.luau"
|
||||||
"StarterPlayer": {
|
|
||||||
"StarterPlayerScripts": {
|
|
||||||
"Client": {
|
|
||||||
"$path": "demo/src/client"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Workspace": {
|
"Workspace": {
|
||||||
|
@ -66,6 +63,11 @@
|
||||||
"$properties": {
|
"$properties": {
|
||||||
"RespectFilteringEnabled": true
|
"RespectFilteringEnabled": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"StarterPlayer": {
|
||||||
|
"StarterPlayerScripts": {
|
||||||
|
"$path": "demo/src/StarterPlayer/StarterPlayerScripts"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
demo/.config/blink
Normal file
18
demo/.config/blink
Normal 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)
|
||||||
|
}
|
|
@ -1,17 +1,15 @@
|
||||||
# example
|
# Demo
|
||||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.4.1.
|
|
||||||
|
|
||||||
## Getting Started
|
## Build with Rojo
|
||||||
To build the place from scratch, use:
|
To build the place, run the following commands from the root of the repository:
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
rojo serve
|
rojo serve
|
||||||
```
|
```
|
||||||
|
|
||||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
|
337
demo/net/client.luau
Normal file
337
demo/net/client.luau
Normal 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
374
demo/net/server.luau
Normal 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,
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
40
demo/src/ReplicatedStorage/std/bt.luau
Normal file
40
demo/src/ReplicatedStorage/std/bt.luau
Normal 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
|
|
@ -1,73 +1,8 @@
|
||||||
--!optimize 2
|
|
||||||
--!native
|
|
||||||
|
|
||||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||||
|
|
||||||
type World = jecs.WorldShim
|
local world = require(script.Parent.world)
|
||||||
type Entity<T = any> = jecs.Entity<T>
|
local sparse = ((world :: any) :: jecs.World).entityIndex.sparse
|
||||||
|
type World = world.World
|
||||||
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
|
|
||||||
|
|
||||||
type Tracker<T> = { track: (world: World, fn: (changes: {
|
type Tracker<T> = { track: (world: World, fn: (changes: {
|
||||||
added: () -> () -> (number, T),
|
added: () -> () -> (number, T),
|
||||||
|
@ -97,7 +32,7 @@ local function diff(a, b)
|
||||||
return false
|
return false
|
||||||
end
|
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 PreviousT = jecs.pair(jecs.Rest, T)
|
||||||
local add = {}
|
local add = {}
|
||||||
local added
|
local added
|
||||||
|
@ -142,7 +77,7 @@ local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
|
||||||
id, new, old = q.next()
|
id, new, old = q.next()
|
||||||
end
|
end
|
||||||
|
|
||||||
local record = world.entityIndex.sparse[id]
|
local record = sparse[id]
|
||||||
local archetype = record.archetype
|
local archetype = record.archetype
|
||||||
local column = archetype.records[PreviousT].column
|
local column = archetype.records[PreviousT].column
|
||||||
local data = if is_trivial then new else table.clone(new)
|
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
|
return tracker
|
||||||
end
|
end
|
||||||
|
|
||||||
local bt
|
return ChangeTracker
|
||||||
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
|
|
||||||
}
|
|
67
demo/src/ReplicatedStorage/std/collect.luau
Normal file
67
demo/src/ReplicatedStorage/std/collect.luau
Normal 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
|
14
demo/src/ReplicatedStorage/std/components.luau
Normal file
14
demo/src/ReplicatedStorage/std/components.luau
Normal 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)
|
11
demo/src/ReplicatedStorage/std/ctx.luau
Normal file
11
demo/src/ReplicatedStorage/std/ctx.luau
Normal 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
|
53
demo/src/ReplicatedStorage/std/handle.luau
Normal file
53
demo/src/ReplicatedStorage/std/handle.luau
Normal 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
|
20
demo/src/ReplicatedStorage/std/init.luau
Normal file
20
demo/src/ReplicatedStorage/std/init.luau
Normal 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
|
19
demo/src/ReplicatedStorage/std/interval.luau
Normal file
19
demo/src/ReplicatedStorage/std/interval.luau
Normal 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
|
18
demo/src/ReplicatedStorage/std/ref.luau
Normal file
18
demo/src/ReplicatedStorage/std/ref.luau
Normal 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
|
31
demo/src/ReplicatedStorage/std/registry.luau
Normal file
31
demo/src/ReplicatedStorage/std/registry.luau
Normal 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,
|
||||||
|
}
|
64
demo/src/ReplicatedStorage/std/scheduler.luau
Normal file
64
demo/src/ReplicatedStorage/std/scheduler.luau
Normal 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
|
5
demo/src/ReplicatedStorage/std/world.luau
Normal file
5
demo/src/ReplicatedStorage/std/world.luau
Normal 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()
|
4
demo/src/ServerScriptService/main.server.luau
Normal file
4
demo/src/ServerScriptService/main.server.luau
Normal 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)
|
49
demo/src/ServerScriptService/systems/mobsMove.luau
Normal file
49
demo/src/ServerScriptService/systems/mobsMove.luau
Normal 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
|
40
demo/src/ServerScriptService/systems/players.luau
Normal file
40
demo/src/ServerScriptService/systems/players.luau
Normal 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
|
30
demo/src/ServerScriptService/systems/spawnMobs.luau
Normal file
30
demo/src/ServerScriptService/systems/spawnMobs.luau
Normal 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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1 +0,0 @@
|
||||||
print("Hello world, from client!")
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
Loading…
Reference in a new issue