Custom User Events

Ensuring Thread Safety with Custom Events

How do I ensure thread safety when pushing custom events from multiple threads?

Abstract art representing computer programming

Ensuring thread safety when pushing custom events from multiple threads is crucial to prevent race conditions and undefined behavior. SDL provides thread-safe functions for this purpose, but we need to use them correctly. Here's a guide on how to safely push custom events from multiple threads:

Use SDL_PushEvent()

The SDL_PushEvent() function is thread-safe, meaning it can be safely called from multiple threads without additional synchronization. However, we need to be careful about how we construct and manage the event data.

#include "UserEvents.h"
#include <SDL.h>
#include <thread>
#include <vector>

void ThreadFunction(int threadId) {
  SDL_Event event;
  event.type = UserEvents::CUSTOM_EVENT;
  event.user.code = threadId;
  event.user.data1 = nullptr;
  event.user.data2 = nullptr;

  if (SDL_PushEvent(&event) < 0) {
    SDL_Log("SDL_PushEvent failed: %s\n",
            SDL_GetError());
  }
}

int main() {
  SDL_Init(SDL_INIT_EVERYTHING);

  std::vector<std::thread> threads;
  for (int i = 0; i < 5; ++i) {
    threads.emplace_back(ThreadFunction, i);
  }

  SDL_Event event;
  bool quit{false};

  while (!quit) {
    while (SDL_PollEvent(&event)) {
      if (event.type == SDL_QUIT) {
        quit = true;
      } else if (event.type ==
                 UserEvents::CUSTOM_EVENT) {
        SDL_Log(
            "Received event from thread %d\n",
            event.user.code);
      }
    }
  }

  for (auto& thread : threads) {
    thread.join();
  }

  SDL_Quit();
  return 0;
}

Be Careful with Event Data

While SDL_PushEvent() is thread-safe, we need to be careful about the data we're attaching to the event. If we're using data1 or data2 to point to dynamically allocated memory or shared resources, we need to ensure that:

  1. The memory remains valid until the event is processed.
  2. Access to shared resources is properly synchronized.

Here's an example of how to safely pass data with the event:

#include "UserEvents.h"
#include <SDL.h>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>

struct ThreadData {
  int value;
  std::mutex mutex;
};

void ThreadFunction(int threadId,
                    ThreadData* data) {
  SDL_Event event;
  event.type = UserEvents::CUSTOM_EVENT;
  event.user.code = threadId;
  event.user.data1 = data;

  {
    std::lock_guard<std::mutex> lock(
        data->mutex);
    data->value = threadId * 10;
  }

  if (SDL_PushEvent(&event) < 0) {
    SDL_Log("SDL_PushEvent failed: %s\n",
            SDL_GetError());
  }
}

int main() {
  SDL_Init(SDL_INIT_EVERYTHING);

  auto threadData =
      std::make_unique<ThreadData>();
  std::vector<std::thread> threads;
  for (int i = 0; i < 5; ++i) {
    threads.emplace_back(ThreadFunction, i,
                         threadData.get());
  }

  SDL_Event event;
  bool quit{false};

  while (!quit) {
    while (SDL_PollEvent(&event)) {
      if (event.type == SDL_QUIT) {
        quit = true;
      } else if (event.type ==
                 UserEvents::CUSTOM_EVENT) {
        auto data = static_cast<ThreadData*>(
          event.user.data1);
        int value;
        {
          std::lock_guard<std::mutex> lock(
              data->mutex);
          value = data->value;
        }
        SDL_Log("Received event from thread %d "
                "with value %d\n",
                event.user.code, value);
      }
    }
  }

  for (auto& thread : threads) {
    thread.join();
  }

  SDL_Quit();
  return 0;
}

In this example, we use a mutex to protect access to shared data. The ThreadData struct is allocated in the main thread and remains valid throughout the program's lifetime.

Use SDL_LockMutex() and SDL_UnlockMutex()

For more complex synchronization needs, SDL provides its own mutex functions that are designed to work well with the SDL event system:

#include "UserEvents.h"
#include <SDL.h>
#include <thread>
#include <vector>

SDL_mutex* gMutex = nullptr;
int gSharedValue = 0;

void ThreadFunction(int threadId) {
  SDL_Event event;
  event.type = UserEvents::CUSTOM_EVENT;
  event.user.code = threadId;

  SDL_LockMutex(gMutex);
  gSharedValue += threadId;
  event.user.data1 =
      reinterpret_cast<void*>(gSharedValue);
  SDL_UnlockMutex(gMutex);

  if (SDL_PushEvent(&event) < 0) {
    SDL_Log("SDL_PushEvent failed: %s\n",
            SDL_GetError());
  }
}

int main() {
  SDL_Init(SDL_INIT_EVERYTHING);
  gMutex = SDL_CreateMutex();

  std::vector<std::thread> threads;
  for (int i = 0; i < 5; ++i) {
    threads.emplace_back(ThreadFunction, i);
  }

  SDL_Event event;
  bool quit{false};

  while (!quit) {
    while (SDL_PollEvent(&event)) {
      if (event.type == SDL_QUIT) {
        quit = true;
      } else if (event.type ==
                 UserEvents::CUSTOM_EVENT) {
        SDL_Log("Received event from thread %d "
                "with shared value %d\n",
                event.user.code,
                reinterpret_cast<intptr_t>(
                  event.user.data1));
      }
    }
  }

  for (auto& thread : threads) {
    thread.join();
  }

  SDL_DestroyMutex(gMutex);
  SDL_Quit();
  return 0;
}

By following these practices, you can safely push custom events from multiple threads while avoiding race conditions and ensuring thread safety in your SDL2-based game.

This Question is from the Lesson:

Custom User Events

Discover how to use the SDL2 event system to handle custom, game-specific interactions

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

This Question is from the Lesson:

Custom User Events

Discover how to use the SDL2 event system to handle custom, game-specific interactions

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:

  • 67 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