AppleyAppley Docs
Docs/Networking

Dedicated Servers

Build dedicated server infrastructure for persistent multiplayer worlds.

22 min read Advanced Updated 2026-07-01 v2.0 ~85 min to complete

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

This is part of the Advanced Documentation collection. Category: Networking. Estimated completion: 85 minutes. Difficulty: Advanced/Professional.

Screenshot

Dedicated Servers
Dedicated Servers

How Professional Games Do It

1

D

e

2

T

h

3

I

m

4

R

u

5

M

a

6

T

r

7

P

e

8

P

e

9

H

a

10

F

o

11

I

m

12

S

e

FluxScript Implementation

Here is how to build this system in Appley using FluxScript (Appley Scripting):

Dedicated Servers — FluxScript
// 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:

Dedicated Servers — Node Graph
[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.