Initial push
This commit is contained in:
parent
5a9167e2f5
commit
8ee75b96ed
51 changed files with 4437 additions and 2 deletions
15
src/client/controllers/receive_data.luau
Normal file
15
src/client/controllers/receive_data.luau
Normal file
|
@ -0,0 +1,15 @@
|
|||
--!strict
|
||||
local data = require("@client/data")
|
||||
local net = require("@net/client")
|
||||
|
||||
local function init()
|
||||
net.replicate_data.on(function(difference)
|
||||
data.apply_difference(difference)
|
||||
end)
|
||||
end
|
||||
|
||||
return {
|
||||
init = init,
|
||||
-- data is important
|
||||
priority = math.huge,
|
||||
}
|
0
src/client/controllers/systems/.gitkeep
Normal file
0
src/client/controllers/systems/.gitkeep
Normal file
14
src/client/data.luau
Normal file
14
src/client/data.luau
Normal file
|
@ -0,0 +1,14 @@
|
|||
--!strict
|
||||
local sapphire_data = require("@pkg/sapphire_data")
|
||||
|
||||
export type data = {
|
||||
coins: number,
|
||||
}
|
||||
|
||||
local template: data = {
|
||||
coins = 0,
|
||||
}
|
||||
|
||||
return sapphire_data.client({
|
||||
template = template,
|
||||
})
|
16
src/client/main.client.luau
Normal file
16
src/client/main.client.luau
Normal file
|
@ -0,0 +1,16 @@
|
|||
--!strict
|
||||
local sapphire = require("@pkg/sapphire")
|
||||
|
||||
local data = require("@client/data")
|
||||
local ecs = require("@pkg/sapphire_jecs")
|
||||
local lifecycles = require("@pkg/sapphire_lifecycles")
|
||||
|
||||
local controllers = script.Parent.controllers
|
||||
|
||||
sapphire()
|
||||
:register_singletons(controllers)
|
||||
:register_singletons(controllers.systems)
|
||||
:use(data)
|
||||
:use(ecs)
|
||||
:use(lifecycles)
|
||||
:start()
|
10
src/client/player.luau
Normal file
10
src/client/player.luau
Normal file
|
@ -0,0 +1,10 @@
|
|||
--!strict
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local components = require("@shared/components")
|
||||
local ecs = require("@pkg/sapphire_jecs")
|
||||
|
||||
local player = Players.LocalPlayer
|
||||
local player_entity = ecs.ref("local-player"):set(components.player, player):id()
|
||||
|
||||
return player_entity
|
265
src/net/client.luau
Normal file
265
src/net/client.luau
Normal file
|
@ -0,0 +1,265 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
--!nolint LocalShadow
|
||||
--#selene: allow(shadowing)
|
||||
-- File generated by Blink v0.14.15 (https://github.com/1Axen/Blink)
|
||||
-- This file is not meant to be edited
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
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.Reliable[0] = table.create(256)
|
||||
function Types.ReadEVENT_replicate_data(): (buffer)
|
||||
-- Read BLOCK: 2 bytes
|
||||
local BLOCK_START = Read(2)
|
||||
local Length = buffer.readu16(RecieveBuffer, BLOCK_START + 0)
|
||||
local Value = buffer.create(Length)
|
||||
buffer.copy(Value, 0, RecieveBuffer, Read(Length), Length)
|
||||
return Value
|
||||
end
|
||||
|
||||
function Types.WriteEVENT_replicate_data(Value: buffer): ()
|
||||
-- Allocate BLOCK: 3 bytes
|
||||
local BLOCK_START = Allocate(3)
|
||||
buffer.writeu8(SendBuffer, BLOCK_START + 0, 0)
|
||||
local Length = buffer.len(Value)
|
||||
buffer.writeu16(SendBuffer, BLOCK_START + 1, Length)
|
||||
Allocate(Length)
|
||||
buffer.copy(SendBuffer, SendOffset, Value, 0, Length)
|
||||
end
|
||||
|
||||
if not RunService:IsRunning() then
|
||||
local NOOP = function() end
|
||||
local Returns = table.freeze({
|
||||
replicate_data = {
|
||||
on = NOOP
|
||||
},
|
||||
})
|
||||
return Returns :: BLINK_EVENTS_SYMBOL
|
||||
end
|
||||
|
||||
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 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
|
||||
local Value: buffer = Types.ReadEVENT_replicate_data()
|
||||
if Events.Reliable[0] ~= nil then
|
||||
Events.Reliable[0](Value)
|
||||
else
|
||||
if #Queue.Reliable[0] > 256 then
|
||||
warn("[Blink]: Event queue of \"replicate_data\" exceeded 256, did you forget to implement a listener?")
|
||||
end
|
||||
table.insert(Queue.Reliable[0], {Value} :: {any})
|
||||
end
|
||||
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)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
local Returns = table.freeze({
|
||||
step_replication = StepReplication,
|
||||
|
||||
replicate_data = {
|
||||
on = function(Listener: (Value: buffer) -> ()): () -> ()
|
||||
Events.Reliable[0] = Listener
|
||||
for Index, Arguments in Queue.Reliable[0] do
|
||||
Listener(table.unpack(Arguments))
|
||||
end
|
||||
Queue.Reliable[0] = {}
|
||||
return function (): ()
|
||||
Events.Reliable[0] = nil
|
||||
end
|
||||
end
|
||||
},
|
||||
})
|
||||
type BLINK_EVENTS_SYMBOL = typeof(Returns)
|
||||
return Returns :: BLINK_EVENTS_SYMBOL
|
304
src/net/server.luau
Normal file
304
src/net/server.luau
Normal file
|
@ -0,0 +1,304 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
--!nolint LocalShadow
|
||||
--#selene: allow(shadowing)
|
||||
-- File generated by Blink v0.14.15 (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")
|
||||
|
||||
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_replicate_data(): (buffer)
|
||||
-- Read BLOCK: 2 bytes
|
||||
local BLOCK_START = Read(2)
|
||||
local Length = buffer.readu16(RecieveBuffer, BLOCK_START + 0)
|
||||
local Value = buffer.create(Length)
|
||||
buffer.copy(Value, 0, RecieveBuffer, Read(Length), Length)
|
||||
return Value
|
||||
end
|
||||
|
||||
function Types.WriteEVENT_replicate_data(Value: buffer): ()
|
||||
-- Allocate BLOCK: 3 bytes
|
||||
local BLOCK_START = Allocate(3)
|
||||
buffer.writeu8(SendBuffer, BLOCK_START + 0, 0)
|
||||
local Length = buffer.len(Value)
|
||||
buffer.writeu16(SendBuffer, BLOCK_START + 1, Length)
|
||||
Allocate(Length)
|
||||
buffer.copy(SendBuffer, SendOffset, Value, 0, Length)
|
||||
end
|
||||
|
||||
if not RunService:IsRunning() then
|
||||
local NOOP = function() end
|
||||
local Returns = table.freeze({
|
||||
replicate_data = {
|
||||
fire = NOOP,
|
||||
fire_all = NOOP,
|
||||
fire_list = NOOP,
|
||||
fire_except = NOOP
|
||||
},
|
||||
})
|
||||
return Returns :: BLINK_EVENTS_SYMBOL
|
||||
end
|
||||
|
||||
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 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)
|
||||
|
||||
|
||||
local Returns = table.freeze({
|
||||
step_replication = StepReplication,
|
||||
|
||||
replicate_data = {
|
||||
fire = function(Player: Player, Value: buffer): ()
|
||||
Load(PlayersMap[Player])
|
||||
Types.WriteEVENT_replicate_data(Value)
|
||||
PlayersMap[Player] = Save()
|
||||
end,
|
||||
fire_all = function(Value: buffer): ()
|
||||
Load()
|
||||
Types.WriteEVENT_replicate_data(Value)
|
||||
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,
|
||||
fire_list = function(List: {Player}, Value: buffer): ()
|
||||
Load()
|
||||
Types.WriteEVENT_replicate_data(Value)
|
||||
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,
|
||||
fire_except = function(Except: Player, Value: buffer): ()
|
||||
Load()
|
||||
Types.WriteEVENT_replicate_data(Value)
|
||||
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,
|
||||
},
|
||||
})
|
||||
type BLINK_EVENTS_SYMBOL = typeof(Returns)
|
||||
return Returns :: BLINK_EVENTS_SYMBOL
|
153
src/net/types.luau
Normal file
153
src/net/types.luau
Normal file
|
@ -0,0 +1,153 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
--!nolint LocalShadow
|
||||
--#selene: allow(shadowing)
|
||||
-- File generated by Blink v0.14.15 (https://github.com/1Axen/Blink)
|
||||
-- This file is not meant to be edited
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
|
||||
}
|
15
src/server/data.luau
Normal file
15
src/server/data.luau
Normal file
|
@ -0,0 +1,15 @@
|
|||
--!strict
|
||||
local sapphire_data = require("@pkg/sapphire_data")
|
||||
|
||||
export type data = {
|
||||
coins: number,
|
||||
}
|
||||
|
||||
local template: data = {
|
||||
coins = 0,
|
||||
}
|
||||
|
||||
return sapphire_data.server({
|
||||
template = template,
|
||||
store_name = "player_data",
|
||||
})
|
16
src/server/main.server.luau
Normal file
16
src/server/main.server.luau
Normal file
|
@ -0,0 +1,16 @@
|
|||
--!strict
|
||||
local sapphire = require("@pkg/sapphire")
|
||||
|
||||
local data = require("@server/data")
|
||||
local ecs = require("@pkg/sapphire_jecs")
|
||||
local lifecycles = require("@pkg/sapphire_lifecycles")
|
||||
|
||||
local services = script.Parent.services
|
||||
|
||||
sapphire()
|
||||
:register_singletons(services)
|
||||
:register_singletons(services.systems)
|
||||
:use(data)
|
||||
:use(ecs)
|
||||
:use(lifecycles)
|
||||
:start()
|
22
src/server/services/replicate_data.luau
Normal file
22
src/server/services/replicate_data.luau
Normal file
|
@ -0,0 +1,22 @@
|
|||
--!strict
|
||||
local data = require("@server/data")
|
||||
local net = require("@net/server")
|
||||
|
||||
local function init()
|
||||
-- this fires every time the data is changed including initial data load
|
||||
data.on_data_changed(function(player)
|
||||
local difference = data.calculate_difference(player)
|
||||
if not difference then
|
||||
-- weird.. but ok
|
||||
return
|
||||
end
|
||||
|
||||
net.replicate_data.fire(player, difference)
|
||||
end)
|
||||
end
|
||||
|
||||
return {
|
||||
init = init,
|
||||
-- data is important
|
||||
priority = math.huge,
|
||||
}
|
31
src/server/services/systems/entitify_players.luau
Normal file
31
src/server/services/systems/entitify_players.luau
Normal file
|
@ -0,0 +1,31 @@
|
|||
--!strict
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local ecs = require("@pkg/sapphire_jecs")
|
||||
local ref = ecs.ref
|
||||
|
||||
local components = require("@shared/components")
|
||||
local cplayer = components.player
|
||||
|
||||
local types = require("@shared/types")
|
||||
local util = require("@shared/util")
|
||||
type player = types.RobloxPlayer
|
||||
type character = types.R15Character
|
||||
|
||||
local player_added = ecs.collect(Players.PlayerAdded)
|
||||
local player_removing = ecs.collect(Players.PlayerRemoving)
|
||||
|
||||
local function system()
|
||||
for _, player: player in player_added do
|
||||
ref(player.UserId):set(cplayer, player)
|
||||
end
|
||||
|
||||
for _, player: player in player_removing do
|
||||
ref(player.UserId):delete()
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
system = util.wrap_system(system),
|
||||
phase = "heartbeat",
|
||||
}
|
33
src/shared/components.luau
Normal file
33
src/shared/components.luau
Normal file
|
@ -0,0 +1,33 @@
|
|||
--!strict
|
||||
local ecs = require("@pkg/sapphire_jecs")
|
||||
local jecs = ecs.jecs
|
||||
local world = ecs.world
|
||||
type entity<T = nil> = ecs.entity<T>
|
||||
|
||||
local types = require("@shared/types")
|
||||
-- change the `Character` type in `types` to whichever character your game is using
|
||||
type character = types.Character
|
||||
type player = types.RobloxPlayer<character>
|
||||
|
||||
return {
|
||||
player = world:component() :: entity<player>,
|
||||
|
||||
world_model = world:component() :: entity<BasePart>,
|
||||
|
||||
-- jecs reexports
|
||||
on_add = jecs.OnAdd,
|
||||
on_remove = jecs.OnRemove,
|
||||
on_set = jecs.OnSet,
|
||||
child_of = jecs.ChildOf,
|
||||
componente = jecs.Component,
|
||||
wildcard = jecs.Wildcard,
|
||||
w = jecs.Wildcard,
|
||||
on_delete = jecs.OnDelete,
|
||||
on_delete_target = jecs.OnDeleteTarget,
|
||||
delete = jecs.Delete,
|
||||
remove = jecs.Remove,
|
||||
name = jecs.Name,
|
||||
rest = jecs.Rest,
|
||||
|
||||
pair = jecs.pair,
|
||||
}
|
182
src/shared/types.luau
Normal file
182
src/shared/types.luau
Normal file
|
@ -0,0 +1,182 @@
|
|||
--!strict
|
||||
export type R15CharacterChildren = {
|
||||
Humanoid: Humanoid & {
|
||||
Animator: Animator,
|
||||
},
|
||||
Shirt: Shirt,
|
||||
Pants: Pants,
|
||||
["Body Colors"]: BodyColors,
|
||||
["Shirt Graphic"]: ShirtGraphic,
|
||||
HumanoidRootPart: Part & {
|
||||
Climbing: Sound,
|
||||
Died: Sound,
|
||||
FreeFalling: Sound,
|
||||
GettingUp: Sound,
|
||||
Jumping: Sound,
|
||||
Landing: Sound,
|
||||
Running: Sound,
|
||||
Splash: Sound,
|
||||
Swimming: Sound,
|
||||
RootRigAttachment: Attachment,
|
||||
},
|
||||
Head: MeshPart & {
|
||||
FaceCenterAttachment: Attachment,
|
||||
FaceFrontAttachment: Attachment,
|
||||
HairAttachment: Attachment,
|
||||
HatAttachment: Attachment,
|
||||
NeckRigAttachment: Attachment,
|
||||
Neck: Motor6D,
|
||||
},
|
||||
LeftFoot: MeshPart & {
|
||||
LeftAnkleRigAttachment: Attachment,
|
||||
LeftFootAttachment: Attachment,
|
||||
LeftFoot: WrapTarget,
|
||||
LeftAnkle: Motor6D,
|
||||
},
|
||||
RightFoot: MeshPart & {
|
||||
RightAnkleRigAttachment: Attachment,
|
||||
RightFootAttachment: Attachment,
|
||||
RightFoot: WrapTarget,
|
||||
RightAnkle: Motor6D,
|
||||
},
|
||||
LeftLowerLeg: MeshPart & {
|
||||
LeftLowerLeg: WrapTarget,
|
||||
},
|
||||
RightLowerLeg: MeshPart & {
|
||||
RightLowerLeg: WrapTarget,
|
||||
},
|
||||
LeftUpperLeg: MeshPart & {
|
||||
LeftUpperLeg: WrapTarget,
|
||||
},
|
||||
RightUpperLeg: MeshPart & {
|
||||
RightUpperLeg: WrapTarget,
|
||||
},
|
||||
LowerTorso: MeshPart & {
|
||||
LowerTorso: WrapTarget,
|
||||
},
|
||||
UpperTorso: MeshPart & {
|
||||
UpperTorso: WrapTarget,
|
||||
},
|
||||
RightHand: MeshPart & {
|
||||
RightHand: WrapTarget,
|
||||
},
|
||||
LeftHand: MeshPart & {
|
||||
LeftHand: WrapTarget,
|
||||
},
|
||||
RightLowerArm: MeshPart & {
|
||||
RightLowerArm: WrapTarget,
|
||||
},
|
||||
RightUpperArm: MeshPart & {
|
||||
RightUpperArm: WrapTarget,
|
||||
},
|
||||
LeftLowerArm: MeshPart & {
|
||||
LeftElbowRigAttachment: Attachment,
|
||||
LeftWristAttachment: Attachment,
|
||||
LeftLowerArm: WrapTarget,
|
||||
LeftElbow: Motor6D,
|
||||
},
|
||||
LeftUpperArm: MeshPart & {
|
||||
LeftElbowRigAttachment: Attachment,
|
||||
LeftShoulderAttachment: Attachment,
|
||||
LeftShoulderRigAttachment: Attachment,
|
||||
LeftUpperArm: WrapTarget,
|
||||
LeftShoulder: Motor6D,
|
||||
},
|
||||
}
|
||||
|
||||
export type R6CharacterChildren = {
|
||||
Humanoid: Humanoid & {
|
||||
Animator: Animator,
|
||||
},
|
||||
Shirt: Shirt,
|
||||
Pants: Pants,
|
||||
["Body Colors"]: BodyColors,
|
||||
["Shirt Graphic"]: ShirtGraphic,
|
||||
HumanoidRootPart: Part & {
|
||||
Climbing: Sound,
|
||||
Died: Sound,
|
||||
FreeFalling: Sound,
|
||||
GettingUp: Sound,
|
||||
Jumping: Sound,
|
||||
Landing: Sound,
|
||||
Running: Sound,
|
||||
Splash: Sound,
|
||||
Swimming: Sound,
|
||||
RootAttachment: Attachment,
|
||||
RootJoint: Motor6D,
|
||||
},
|
||||
Head: Part & {
|
||||
FaceCenterAttachment: Attachment,
|
||||
FaceFrontAttachment: Attachment,
|
||||
HairAttachment: Attachment,
|
||||
HatAttachment: Attachment,
|
||||
Mesh: SpecialMesh,
|
||||
face: Decal,
|
||||
},
|
||||
Torso: Part & {
|
||||
BodyBackAttachment: Attachment,
|
||||
BodyFrontAttachment: Attachment,
|
||||
LeftCollarAttachment: Attachment,
|
||||
NeckAttachment: Attachment,
|
||||
RightCollarAttachment: Attachment,
|
||||
WaistBackAttachment: Attachment,
|
||||
WaistCenterAttachment: Attachment,
|
||||
WaistFrontAttachment: Attachment,
|
||||
roblox: Decal,
|
||||
["Left Hip"]: Motor6D,
|
||||
["Left Shoulder"]: Motor6D,
|
||||
Neck: Motor6D,
|
||||
["Right Hip"]: Motor6D,
|
||||
["Right Shoulder"]: Motor6D,
|
||||
},
|
||||
["Left Arm"]: Part & {
|
||||
LeftGripAttachment: Attachment,
|
||||
LeftShoulderAttachment: Attachment,
|
||||
},
|
||||
["Left Leg"]: Part & {
|
||||
LeftFootAttachment: Attachment,
|
||||
},
|
||||
["Right Arm"]: Part & {
|
||||
RightGripAttachment: Attachment,
|
||||
RightShoulderAttachment: Attachment,
|
||||
},
|
||||
["Right Leg"]: Part & {
|
||||
RightFootAttachment: Attachment,
|
||||
},
|
||||
}
|
||||
|
||||
export type R15Character = Model & Instance & R15CharacterChildren
|
||||
export type R6Character = Model & Instance & R6CharacterChildren
|
||||
-- change it to whatever youre using
|
||||
export type Character = R15Character
|
||||
|
||||
export type RobloxPlayer<Character = Character> = Player & {
|
||||
PlayerGui: PlayerGui & StarterGui & {
|
||||
BubbleChat: ScreenGui,
|
||||
Chat: ScreenGui,
|
||||
Freecam: ScreenGui,
|
||||
},
|
||||
PlayerScripts: PlayerScripts & {
|
||||
BubbleChat: LocalScript,
|
||||
ChatScript: LocalScript,
|
||||
PlayerScriptsLoader: LocalScript,
|
||||
RbxCharacterSounds: LocalScript,
|
||||
PlayerModule: ModuleScript,
|
||||
},
|
||||
Character: Character?,
|
||||
CharacterAdded: RBXScriptSignal<Character>,
|
||||
}
|
||||
|
||||
export type Ok<T> = {
|
||||
kind: "ok",
|
||||
value: T,
|
||||
}
|
||||
|
||||
export type Err<E> = {
|
||||
kind: "err",
|
||||
value: E,
|
||||
}
|
||||
|
||||
export type Result<T, E> = Ok<T> | Err<E>
|
||||
|
||||
return {}
|
28
src/shared/util.luau
Normal file
28
src/shared/util.luau
Normal file
|
@ -0,0 +1,28 @@
|
|||
--!strict
|
||||
|
||||
--[=[
|
||||
Useful for wrapping systems for schedulers which take a function returning a system.
|
||||
|
||||
(
|
||||
```lua
|
||||
-- for example
|
||||
local function system(world: world)
|
||||
return function(dt: number)
|
||||
...
|
||||
end
|
||||
end
|
||||
```
|
||||
)
|
||||
|
||||
@param system
|
||||
@return () -> (T...) -> ()
|
||||
]=]
|
||||
local function wrap_system<T...>(system: (T...) -> ()): () -> (T...) -> ()
|
||||
return function()
|
||||
return system
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
wrap_system = wrap_system,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue