Skip to main content

Overview

PocketMine-MP is built on a single-threaded tick-based architecture that manages worlds, entities, players, and network operations. The server runs at a target rate of 20 ticks per second (TPS), processing game logic synchronously while offloading heavy operations to async workers.

Core Components

Server Class

The Server class (pocketmine\Server) is the central hub that manages all server operations.
Server.php:183
class Server {
    public const TARGET_TICKS_PER_SECOND = 20;
    public const TARGET_SECONDS_PER_TICK = 1 / self::TARGET_TICKS_PER_SECOND;
    
    private PluginManager $pluginManager;
    private WorldManager $worldManager;
    private Network $network;
    private AsyncPool $asyncPool;
    private SimpleCommandMap $commandMap;
    private CraftingManager $craftingManager;
    // ...
}
The server maintains a constant 50ms tick cycle (1000ms / 20 ticks). Between ticks, the server sleeps using a SleeperHandler that can be woken up by network packets or other events.

Key Managers

Manages plugin loading, enabling, disabling, and event registration.
$pluginManager = $server->getPluginManager();
$plugin = $pluginManager->getPlugin("MyPlugin");
Handles world loading, unloading, chunk management, and world ticking.
$worldManager = $server->getWorldManager();
$world = $worldManager->getWorldByName("world");
Manages network interfaces (RakLib), packet broadcasting, and compression.
$network = $server->getNetwork();
$interfaces = $network->getInterfaces();
Routes commands to their handlers and manages command registration.
$commandMap = $server->getCommandMap();
$commandMap->register("myplugin", new MyCommand());

Threading Model

Main Thread

The main thread runs the tick loop which processes:
  1. Network packets - Incoming player actions
  2. World ticking - Block updates, entity movement, chunk loading
  3. Entity updates - Player movement, mob AI
  4. Scheduled tasks - Plugin tasks registered via scheduler
  5. Event calls - Synchronous event handlers
// The main tick cycle (simplified)
while ($server->isRunning()) {
    $tickTime = microtime(true);
    
    $server->tick();  // Process one server tick
    
    $sleepTime = Server::TARGET_SECONDS_PER_TICK - (microtime(true) - $tickTime);
    if ($sleepTime > 0) {
        $server->sleepUntilNextTick($sleepTime);
    }
}

Async Workers

The AsyncPool manages worker threads for CPU-intensive operations:
  • Chunk generation - World terrain generation
  • Chunk I/O - Loading/saving chunks from/to disk
  • Packet compression - Batch packet compression (ZLIB)
  • Player authentication - JWT verification for Xbox Live
use pocketmine\scheduler\AsyncTask;

class MyAsyncTask extends AsyncTask {
    public function onRun(): void {
        // Runs on worker thread
        $this->setResult("Heavy computation result");
    }
    
    public function onCompletion(): void {
        // Runs back on main thread
        $result = $this->getResult();
    }
}

$server->getAsyncPool()->submitTask(new MyAsyncTask());
Never access server state (players, worlds, entities) from worker threads. Use onCompletion() to process results on the main thread.

Tick Cycle

Each tick processes operations in a specific order:

Tick Counter

The server maintains a tick counter starting from 0:
$currentTick = $server->getTick();
$server->getLogger()->info("Current tick: $currentTick");

TPS Monitoring

$currentTPS = $server->getTicksPerSecond();
if ($currentTPS < Server::TARGET_TICKS_PER_SECOND * 0.8) {
    $server->getLogger()->warning("Server is running slowly! TPS: $currentTPS");
}
The server logs warnings when TPS drops below 12 TPS (60% of target) for more than 5 seconds.

Singleton Pattern

The Server instance is globally accessible:
$server = Server::getInstance();
$logger = $server->getLogger();
$pluginManager = $server->getPluginManager();
While the singleton pattern provides convenience, plugins should prefer dependency injection through their constructor where possible.

Memory Management

The MemoryManager monitors and controls memory usage:
$memoryManager = $server->getMemoryManager();

// Trigger garbage collection
$memoryManager->triggerGarbageCollector();

// Check memory usage
$usage = $memoryManager->getUsage();

Auto-GC Triggers

  • Low memory threshold reached
  • Every 30 minutes (configurable)
  • Manual trigger via command

Component Lifecycle

Example: Accessing Components

use pocketmine\Server;
use pocketmine\plugin\PluginBase;

class MyPlugin extends PluginBase {
    protected function onEnable(): void {
        $server = $this->getServer();
        
        // Access managers
        $worldManager = $server->getWorldManager();
        $pluginManager = $server->getPluginManager();
        $commandMap = $server->getCommandMap();
        
        // Get server info
        $this->getLogger()->info("Server tick rate: " . Server::TARGET_TICKS_PER_SECOND);
        $this->getLogger()->info("Current TPS: " . $server->getTicksPerSecond());
        $this->getLogger()->info("Online players: " . count($server->getOnlinePlayers()));
    }
}

Best Practices

Do

  • Use async tasks for heavy operations
  • Cache frequently accessed data
  • Batch operations when possible
  • Monitor TPS impact of your code

Don't

  • Block the main thread
  • Access thread-unsafe objects from workers
  • Create excessive scheduled tasks
  • Perform I/O on the main thread

Build docs developers (and LLMs) love