Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions benchmark/default.project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "SatsetBenchmark",
"tree": {
"$className": "DataModel",
"ServerScriptService": {
"$className": "ServerScriptService",
"Server": {
"$path": "src/server"
}
},
"StarterPlayer": {
"$className": "StarterPlayer",
"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",
"Client": {
"$path": "src/client"
}
}
},
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"Shared": {
"$path": "src/shared",
"GetRecieved": {
"$className": "RemoteFunction"
},
"Generate": {
"$className": "RemoteEvent"
}
},
"Packages": {
"$path": "../Packages",
"Satset": {
"$path": "../src"
}
}
}
}
}
175 changes: 175 additions & 0 deletions benchmark/src/client/init.client.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
-- Benchmark client. Fires 1000 events per frame for 10 seconds per tool per bench.
-- Measures FPS and bandwidth (Kbps) at percentile intervals.
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local StarterGui = game:GetService("StarterGui")
local Stats = game:GetService("Stats")

local Satset = require(ReplicatedStorage.Packages.Satset)
Satset.start()

local MAXIMUM_FRAMERATE = 60

local Benches, Modes

-- Minimize rendering overhead to isolate network performance.
local Camera = workspace.CurrentCamera
Camera.FieldOfView = 1
Camera.CFrame = CFrame.new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
Players.LocalPlayer.PlayerGui:ClearAllChildren()
for _, child in Players.LocalPlayer.PlayerScripts:GetChildren() do
if child ~= script then
child:Destroy()
end
end

for _, item in Enum.CoreGuiType:GetEnumItems() do
while true do
local ok = pcall(function()
StarterGui:SetCoreGuiEnabled(item, false)
end)
if ok then
break
end
end
end

type Benchmark = {
Sent: number,
Recieve: number,
Bandwidth: { number },
Framerate: { number },
}

type Results = {
[string]: {
[string]: Benchmark,
},
}

local function percentile(samples: { number }, pct: number)
assert((pct // 1) == pct and pct >= 0 and pct <= 100, "Percentile must be an integer between 0 and 100")
local index = ((#samples * (pct / 100)) // 1)
index = math.max(index, 1)
return samples[index]
end

local function waitForDrain()
print("Waiting for packets to drain.")
while Stats.DataSendKbps > 0.5 do
RunService.Heartbeat:Wait()
end
task.wait(5)
end

local function runBenchmark(tool: string, bench: string): Benchmark
local total = 0
local frames = 0
local bandwidth = {}
local framerates = {}

local sent = 0
local data = Benches[bench]
local events = Modes[tool]
local event = events[bench]
local method

if tool == "roblox" then
method = function(d)
event:FireServer(d)
end
elseif tool == "bridgenet2" then
-- BridgeNet2 client: bridge:Fire(data)
method = function(d)
event:Fire(d)
end
elseif tool == "warp" then
-- Warp client: client:Fire(reliable, ...)
method = function(d)
event:Fire(true, d)
end
else
-- Satset uses .fire(), ByteNet uses .send()
method = event.fire or event.Fire or event.send
end

local connection = RunService.PostSimulation:Connect(function(dt: number)
total += dt
frames += 1

if total >= 1 then
total -= 1
local scale = MAXIMUM_FRAMERATE / frames
table.insert(bandwidth, Stats.DataSendKbps * scale)
table.insert(framerates, frames)
frames = 0
end

for _ = 1, 1000 do
sent += 1
method(data)
end
end)

task.wait(10)
connection:Disconnect()
print("> Finished running with " .. tool)

-- Sort for percentile calculation.
table.sort(bandwidth)
table.sort(framerates, function(a, b)
return a > b
end)

local frameratePercentiles = {}
local bandwidthPercentiles = {}

for _, pct in { 50, 0, 80, 90, 95, 100 } do
table.insert(bandwidthPercentiles, percentile(bandwidth, pct))
table.insert(frameratePercentiles, percentile(framerates, pct))
end

return {
Sent = sent,
Recieve = 0,
Bandwidth = bandwidthPercentiles,
Framerate = frameratePercentiles,
}
end

local function runAll()
Benches = require(ReplicatedStorage.Shared.benches)
Modes = require(ReplicatedStorage.Shared.modes)
local results: Results = {}

for bench in Benches do
warn("Running " .. bench .. " benchmark")
results[bench] = {}

for tool in Modes do
print("> Running with " .. tool)
results[bench][tool] = runBenchmark(tool, bench)
waitForDrain()
end

waitForDrain()
end

-- Pull server-side receive counts for loss calculation.
local serverReceived = ReplicatedStorage.Shared.GetRecieved:InvokeServer()
for bench, tools in serverReceived do
for tool, count in tools do
if results[bench] and results[bench][tool] then
results[bench][tool].Recieve = count
end
end
end

print("Finished running benchmarks, generating results...")
ReplicatedStorage.Shared.Generate:FireServer(HttpService:JSONEncode(results))
end

task.wait(2)
runAll()
106 changes: 106 additions & 0 deletions benchmark/src/server/init.server.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
-- Benchmark server. Receives data from all modes and validates correctness.
local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Satset = require(ReplicatedStorage.Packages.Satset)
Satset.start()

local Benches = require(ReplicatedStorage.Shared.benches)
local Modes = require(ReplicatedStorage.Shared.modes)

local function compareTables(a: any, b: any): boolean
if a == b then
return true
end
if type(a) ~= type(b) then
return false
end
if type(a) ~= "table" then
return false
end

local keys = {}
for key, value in a do
local other = b[key]
if other == nil or not compareTables(value, other) then
return false
end
keys[key] = true
end

for key in b do
if not keys[key] then
return false
end
end

return true
end

local function compareValues(a: any, b: any): boolean
if type(a) == "table" or type(b) == "table" then
return compareTables(a, b)
end
return a == b
end

-- Track received counts per bench per tool for loss calculation.
local received = {}
for name, data in Benches do
local benchReceived = {}
received[name] = benchReceived

local function onReceive(tool: string)
benchReceived[tool] = 0

return function(player, value)
benchReceived[tool] += 1
if benchReceived[tool] > 1 then
return
end

-- ByteNet swaps argument order.
if tool == "bytenet" then
player, value = value, player
end

if not compareValues(data, value) then
warn("Received incorrect data with " .. tool .. " for " .. name)
else
print("> " .. tool .. " passed " .. name .. " validation!")
end
end
end

for tool, events in Modes do
local event = events[name]
local callback = onReceive(tool)

if tool == "roblox" then
event.OnServerEvent:Connect(callback)
elseif tool == "bridgenet2" then
-- BridgeNet2: bridge:Connect(callback(player, data))
event:Connect(callback)
elseif tool == "warp" then
-- Warp: server:Connect(callback(player, ...))
event:Connect(callback)
elseif event.listen then
-- Satset and ByteNet use .listen()
event.listen(callback)
end
end
end

-- Client pulls received counts to compute packet loss.
ReplicatedStorage.Shared.GetRecieved.OnServerInvoke = function()
return received
end

-- Client sends JSON results, server writes them out.
ReplicatedStorage.Shared.Generate.OnServerEvent:Connect(function(_player, json)
local output = Instance.new("StringValue")
output.Name = "Result"
output.Value = json
output.Parent = game
print("Generated results")
end)
6 changes: 6 additions & 0 deletions benchmark/src/shared/benches/Booleans.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- 1000 boolean values. Tests minimal-payload throughput and packing efficiency.
local Array = {}
for _ = 1, 1000 do
table.insert(Array, math.random() > 0.5)
end
return Array
13 changes: 13 additions & 0 deletions benchmark/src/shared/benches/Entities.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- 100 entities, each with 6 u8 fields. Simulates a typical NPC/player sync payload.
local Array = {}
for _ = 1, 100 do
table.insert(Array, {
id = math.random(1, 255),
x = math.random(1, 255),
y = math.random(1, 255),
z = math.random(1, 255),
orientation = math.random(1, 255),
animation = math.random(1, 255),
})
end
return Array
12 changes: 12 additions & 0 deletions benchmark/src/shared/benches/Mixed.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Realistic game packet: player state update with mixed types.
-- Simulates what a typical game sends per player per frame.
return {
id = math.random(1, 255),
health = math.random(0, 100),
position = Vector3.new(math.random() * 2048 - 1024, math.random() * 512, math.random() * 2048 - 1024),
velocity = Vector3.new(math.random() * 100 - 50, math.random() * 100 - 50, math.random() * 100 - 50),
name = "Player_" .. tostring(math.random(1000, 9999)),
isRunning = math.random() > 0.5,
animation = math.random(1, 255),
team = math.random(1, 4),
}
2 changes: 2 additions & 0 deletions benchmark/src/shared/benches/SingleValue.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Single u8 value. Tests per-call overhead with minimal payload.
return math.random(1, 255)
13 changes: 13 additions & 0 deletions benchmark/src/shared/benches/Strings.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- 100 short strings (8-32 chars). Tests variable-length serialization.
local CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"
local Array = {}
for _ = 1, 100 do
local len = math.random(8, 32)
local chars = table.create(len)
for j = 1, len do
local idx = math.random(1, #CHARS)
chars[j] = string.sub(CHARS, idx, idx)
end
table.insert(Array, table.concat(chars))
end
return Array
6 changes: 6 additions & 0 deletions benchmark/src/shared/benches/Vectors.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- 100 Vector3 positions. Tests float serialization throughput.
local Array = {}
for _ = 1, 100 do
table.insert(Array, Vector3.new(math.random() * 2048 - 1024, math.random() * 512, math.random() * 2048 - 1024))
end
return Array
9 changes: 9 additions & 0 deletions benchmark/src/shared/benches/init.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- This file is part of the Satset networking library and is licensed under MIT License; see LICENSE.txt for details
-- Benchmark bench loader. Resets random seed for deterministic data across server/client.

local Benches = {}
for _, Bench in script:GetChildren() do
math.randomseed(0)
Benches[Bench.Name] = require(Bench)
end
return Benches
Loading
Loading