Dedicated Servers
Build dedicated server infrastructure for persistent multiplayer worlds.
Introduction
Games like Rust, Ark, and MMOs run dedicated servers that persist game state 24/7. This guide explains how professional servers manage connections, persist world state, and handle server ticks — then builds a dedicated server architecture in Appley.
Advanced Guide 8 of 25
Screenshot
How Professional Games Do It
D
e
T
h
I
m
R
u
M
a
T
r
P
e
P
e
H
a
F
o
I
m
S
e
FluxScript Implementation
Here is how to build this system in Appley using FluxScript (Appley Scripting):
// Dedicated server architecture (headless, no rendering)
const TICK_RATE = 20 // 20 Hz simulation
const SNAPSHOT_INTERVAL = 0.05 // Send state every 50ms
const SAVE_INTERVAL = 300 // Save world every 5 minutes
tickTimer = 0
snapshotTimer = 0
saveTimer = 0
on start {
// Load persisted world state
worldState = database.load("world_state")
if worldState != null {
restoreWorld(worldState)
} else {
generateNewWorld()
}
// Start accepting connections
network.startServer({
port: 443,
maxPlayers: 100,
password: serverPassword
})
print("Dedicated server started: tick=" + TICK_RATE + "Hz")
}
// Main server loop — fixed tick rate
on update(dt) {
// Accumulate time for fixed tick
tickTimer += dt
while tickTimer >= 1.0 / TICK_RATE {
tickTimer -= 1.0 / TICK_RATE
serverTick(1.0 / TICK_RATE)
}
}
func serverTick(dt) {
// 1. Process incoming client inputs
for clientId in connectedClients {
while true {
input = network.receive(clientId)
if input == null break
processClientInput(clientId, input)
}
// Heartbeat check — disconnect dead clients
if time.now() - clients[clientId].lastPing > 10 {
disconnectClient(clientId, "timeout")
}
}
// 2. Step server physics
physics.step(dt)
// 3. Run AI systems
for enemy in world.findAll("enemy") {
enemy.think(dt)
enemy.act(dt)
}
// 4. Broadcast state snapshots
snapshotTimer += dt
if snapshotTimer >= SNAPSHOT_INTERVAL {
snapshotTimer = 0
broadcastSnapshot()
}
// 5. Periodic world save
saveTimer += dt
if saveTimer >= SAVE_INTERVAL {
saveTimer = 0
saveWorld()
}
}
on networkEvent("clientJoin", data) {
clientId = data.clientId
// Create player object
player = world.spawn("player", spawnPoint)
players[clientId] = player
clients[clientId] = { lastPing: time.now(), player: player }
// Send initial world state to new client
initialState = {
playerId: clientId,
worldObjects: serializeWorld(),
otherPlayers: serializePlayers()
}
network.send(clientId, "initialState", initialState)
// Notify other clients
network.broadcast("playerJoined", { id: clientId, name: data.name })
}
on networkEvent("clientInput", data) {
clients[data.clientId].lastPing = time.now()
pendingInputs[data.clientId] = data.input
}
func processClientInput(clientId, input) {
player = players[clientId]
// Validate input合理性
if input.moveSpeed > maxSpeed {
log("Suspicious input from " + clientId + ": speed=" + input.moveSpeed)
return // Reject impossible input
}
// Apply input to server-side player
player.setInput(input)
}
func saveWorld() {
state = {
timestamp: time.now(),
objects: serializeAllObjects(),
players: serializePlayers(),
ai: serializeAIState()
}
database.save("world_state", state)
print("World saved: " + state.objects.length + " objects")
}
func disconnectClient(clientId, reason) {
// Save player state
database.save("player_" + clientId, serializePlayer(players[clientId]))
// Remove from world
players[clientId].destroy()
players[clientId] = null
clients[clientId] = null
// Notify others
network.broadcast("playerLeft", { id: clientId, reason: reason })
}Node Graph Equivalent
The same system implemented as a Node Graph in Appley Node Scripting:
[On Start] --> [Load: world state from database]
--> [Branch: exists?]
+-- True --> [Restore World]
+-- False --> [Generate New World]
--> [Start Server: port 443, max 100 players]
[On Update] --> [Accumulate: tick timer]
--> [While: timer >= 1/TICK_RATE]
--> [serverTick: fixed dt]
[Function: serverTick]
--> [1. Process: client inputs + heartbeat check]
--> [2. Step: server physics]
--> [3. Run: AI systems]
--> [4. Timer: 50ms --> broadcastSnapshot]
--> [5. Timer: 300s --> saveWorld to database]
[On NetworkEvent "clientJoin"]
--> [Spawn: player object]
--> [Send: initial world state to client]
--> [Broadcast: playerJoined to others]
[On NetworkEvent "clientInput"]
--> [Update: lastPing]
--> [Validate: input合理性 (anti-cheat)]
--> [Apply: to server-side player]
[Function: disconnectClient]
--> [Save: player state to database]
--> [Destroy: player object]
--> [Broadcast: playerLeft]Professional Optimization Tips
How AAA studios optimize these systems — applicable to your Appley project:
- Run the server headless (no rendering). A headless server dedicates 100% of CPU to simulation and networking. Rendering on a server wastes resources and limits player count.
- Use a fixed tick rate independent of frame rate. The server simulates at 20Hz regardless of hardware speed. This ensures consistent gameplay across different server hardware.
- Save world state periodically (every 5-15 minutes) and on shutdown. If the server crashes, players lose at most 5 minutes of progress. Use async database writes to avoid tick delays.
- Validate all client inputs for anti-cheat. Check max movement speed, fire rate, and impossible actions. Log suspicious behavior for review. Server authority is your primary anti-cheat defense.
Common Mistakes
Rendering on the server
Never render graphics on a dedicated server. Rendering wastes GPU/CPU that should serve players. Run headless with simulation-only code.
Variable tick rate
If the server tick rate varies with hardware, gameplay differs between servers. Use a fixed tick rate (20Hz) and accumulate time to maintain consistency.
