SDL2 Timers and Callbacks

Syncing Timers in Multiplayer

What's the best way to handle timer-based events in a multiplayer game where timing needs to be synchronized?

Abstract art representing computer programming

Synchronizing timers in multiplayer games requires careful consideration of network latency and state management. Here's a pattern that helps maintain consistency across clients while being resistant to network issues.

Basic Approach

Instead of letting each client manage its own timers independently, we synchronize based on a shared start time:

#include <SDL.h>

#include <iostream>

class NetworkSpawner {
public:
  struct SpawnConfig {
    Uint32 StartTime;
    Uint32 Interval;
    int WaveSize;
  };

  void StartSpawning(
    const SpawnConfig& Config) {
    MyConfig = Config;
    IsSpawning = true;
    LastSpawnTime = Config.StartTime;

    std::cout << "Starting wave at time "
      << Config.StartTime << "ms\n";
  }

  void Update() {
    if (!IsSpawning) { return; }

    Uint32 CurrentTime{SDL_GetTicks()};
    Uint32 TimeSinceStart{
      CurrentTime - MyConfig.StartTime};
    Uint32 ExpectedSpawns{
      TimeSinceStart / MyConfig.Interval};

    // Spawn any missing enemies to catch up
    while (SpawnCount < ExpectedSpawns &&
      SpawnCount < MyConfig.WaveSize) {
      SpawnEnemy(CurrentTime);
      SpawnCount++;
    }

    // Check if wave is complete
    if (SpawnCount >= MyConfig.WaveSize) {
      IsSpawning = false;
    }
  }

private:
  void SpawnEnemy(Uint32 Time) {
    std::cout << "Spawning enemy at time "
      << Time << "ms\n";
    LastSpawnTime = Time;
  }

  SpawnConfig MyConfig;
  bool IsSpawning{false};
  Uint32 LastSpawnTime{0};
  Uint32 SpawnCount{0};
};

// Simulate network message received from server
void HandleServerMessage(
  NetworkSpawner& Spawner) {
  NetworkSpawner::SpawnConfig Config;
  Config.StartTime = SDL_GetTicks();
  Config.Interval = 1000;
  Config.WaveSize = 3;
  Spawner.StartSpawning(Config);
}

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_TIMER);

  NetworkSpawner Spawner;
  HandleServerMessage(Spawner);

  // Simulate game loop
  for (int i = 0; i < 4; ++i) {
    Spawner.Update();
    SDL_Delay(1000);
  }

  SDL_Quit();
  return 0;
}
Starting wave at time 0ms
Spawning enemy at time 1008ms
Spawning enemy at time 2008ms
Spawning enemy at time 3009ms

Key Concepts

Server Authority: The server sends:

  • Wave start times
  • Spawn intervals
  • Number of enemies to spawn

Time Synchronization: Clients convert server time to local time:

Uint32 ServerToLocalTime(Uint32 ServerTime) {
  return ServerTime + EstimatedTimeOffset;
}

Catch-up Mechanism: If a client falls behind (lag spike, etc), it can catch up by spawning multiple enemies in one frame, maintaining consistency with other clients.

State Verification: Periodically verify game state with the server:

void VerifyState(const ServerState& State) {
  if (SpawnCount != State.ExpectedSpawns) {
    // Reconcile difference
    SpawnCount = State.ExpectedSpawns;
  }
}

Remember to:

  • Account for network latency in timing calculations
  • Handle disconnections gracefully
  • Consider interpolation for smooth visual presentation
  • Implement proper error handling for desyncs

This approach provides a good balance between responsiveness and consistency while being resistant to network issues.

Answers to questions are automatically generated and may not have been reviewed.

sdl2-promo.jpg
Part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 62 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved