Single Game Instancing

What do you use in order to guarantee that only one instance of your game is running?

  • I see programs like Gnome-Mahjong and Firefox Browser both have the ability to intercept the launch of another instance of their programs. In the case of Gnome-Mahjong it even re-shuffles the current game, just as further proof that the program itself was made aware of the relaunch.

I’m curious if there is a cross-platform solution.

OK. This obviously is not a new problem, a quick search brings up a half dozen solutions.
The one that appears most cross-platform seems to be called “Port Squatting”.

I’m getting pulled away from the computer, I’ll edit this post if I get a chance to make some example code. It looks like SDL_Net can be used here.

“Port Squatting” is extremely simple, here’s some sample code:

#include <SDL3/SDL.h>
#include <SDL3_net/SDL_net.h>

bool isFirstInstance()
{
	// I chose this port number at random
	// There's a slight possibility that this port is used by another program,
	// I don't know if there's a guaranteed resolution to this though.
	NET_Server * server = NET_CreateServer(NULL, 58147);
	if(server)
	{
		return true;
	}
	return false;
}

int main()
{
	SDL_Init(SDL_INIT_VIDEO);
	NET_Init();
	SDL_Window * window = SDL_CreateWindow("Not First", 400, 400, SDL_WINDOW_RESIZABLE);
	SDL_Renderer * renderer = SDL_CreateRenderer(window, 0);
	SDL_SetRenderVSync(renderer, 1);

	SDL_Color bgColor = {200, 200, 120, 255};
	SDL_Color firstColor = {255, 100, 100, 255};

	if(isFirstInstance())
	{
		bgColor = firstColor;
		SDL_SetWindowTitle(window, "First");
	}

	bool run = true;
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_EVENT_QUIT:
					run = false;
					break;
			}

		}
		SDL_SetRenderDrawColor(renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);
		SDL_RenderClear(renderer);
		SDL_RenderPresent(renderer);
	}
	NET_Quit();

	SDL_Quit();
}
  • I chose to not have the extra window close, but rather be aware that it is not the only instance in play. (Title and background color changed as evidence)
  • I expected it to be more difficult which is why I created the wrapper function “isFirstInstance()”, but this function is just an annoyance if you want to further develop a server-client relation between the windows.

Why it works:
Only one server is allowed to be open per port, so any subsequent program that tries to open a server on that port is guaranteed that NET_CreateServer() will fail and return NULL.
This is considered a reliable method because the port is automatically closed when the program exits (even if the program crashes to exit)

Edit:
I suppose the issue I bring up in the comments could be solved by completing the client-server handshake, and confirming that the server is an instance of your program. If it is not, then have a couple of follow-up ports to attempt.

I just use old fashioned semaphores.

#include <fstream>
#include <iostream>

#include <boost/interprocess/sync/file_lock.hpp>
#include <SDL3/SDL_main.h>

#include "MyGame.h"

int SDL_main(int argc, char *argv[])
{
    { std::ofstream ofs("lock.file"); }
    boost::interprocess::file_lock flock("lock.file");
    if(!flock.try_lock())
    {
        std::cout << "Already running." << std::endl;
        return 1;
    }

    RunGame();

    return 0;
}
1 Like

On Windows, I use a mutex as a kernel object (it’s the best solution for this platform) and a custom message type to activate the existing game window. For other platforms, I’ll use a similar solution, also based on system objects and their APIs (I hate dependencies).

Below is some code from my game project, modified for the purposes of this example. Snippets are in low level Free Pascal, so you can directly translate them to C and other system languages.


Few constants

In order to correctly create and detect the existence of a mutex and the game window of another instance, we need the mutex name (a GUID, to ensure uniqueness), as well as the window class name (also a GUID, for the same reason) and the window title. We also need to reserve a message code so we can send a message to another instance’s window and activate it:

const
  BAS_INSTANCE_MUTEX_NAME = '{829376B4-0B76-427C-AD57-CEF8CDD912CB}'; // Example mutex name (generate your own).
  
const
  BAS_WINDOW_CLASS = '{A5E20472-398E-4329-93EA-F1850290C4E7}'; // Example window class (generate your own).
  BAS_WINDOW_TITLE = 'Bastards: When the Nightmare Comes';     // Game window's title.

const
  WM_BAS_WINDOW_ACTIVATE = WM_APP + 1; // Request to activate the window from another instance of the game.

Few variables

We need two variables that live for the entire session (in my case, these are local module variables), one holding a mutex handle and the other holding a flag indicating whether the mutex already exists in the system:

var
  Mutex:       TBas_UInt64; // Mutex handle.
  MutexExists: TBas_Bool8;  // Specifies if the mutex was created by another game instance.

Opening the instance subsystem

The first step is to initialize the variables, which creates a mutex object and determines whether it is the first one or if another instance has already created it:

function Bas_InstanceOpen (): TBas_Bool8; inline;
begin
  Mutex  := CreateMutexA(nil, True, BAS_INSTANCE_MUTEX_NAME);
  Result := Mutex <> 0;

  if Result then
  begin
    MutexExists := GetLastError() = ERROR_ALREADY_EXISTS;

    if MutexExists then
      CloseHandle(Mutex);
  end;
end;

Quitting the instance subsystem

Executed only if the instance subsystem has been initialized correctly. In my code, I add a pointer to this function to the stack of finalizer functions (more on that later):

procedure Bas_InstanceQuit (); inline;
begin
  SDL_UnregisterApp();
  CloseHandle(Mutex);
end;

Initialization of the instance subsystem

Currently, the sole purpose of this function is to instruct SDL to register a custom window class. A custom window class is needed to detect and activate the game window of an already running instance (relying solely on the window title is not safe):

function Bas_InstanceInitialize (): TBas_Bool8; inline;
begin
  Result := SDL_RegisterApp(BAS_WINDOW_CLASS, 0, nil);
end;

Activating a window of an already running instance

This function is responsible for finding the existing game window based on a custom window class and its title. If the window is located and we obtain its handle, we send a message to it to activate it:

procedure Bas_InstanceActivate (); inline;
var
  WindowHandle: TBas_UInt64;
begin
  WindowHandle := FindWindowA(BAS_WINDOW_CLASS, BAS_WINDOW_TITLE);

  if WindowHandle <> 0 then
    PostMessageA(WindowHandle, WM_BAS_WINDOW_ACTIVATE, 0, 0);
end;

Checking for the existence of a mutex

This is a simple getter that returns the status of a mutex (i.e., another game instance), which is stored in a local module variable:

function Bas_InstanceGetExists (): TBas_Bool8; inline;
begin
  Result := MutexExists;
end;

Session main code

Below is the order and purpose of executing all the functions listed above, as well as determining whether to continue booting the game (if no other instance exists) or to abort the boot process (if another instance is detected):

function Bas_SessionOpen (): TBas_Bool8;
begin
  Result := False;

  // If the mutex failed to create, abort booting.
  if not Bas_InstanceOpen() then
  begin
    // Display a system message box that the game instance cannot be properly opened.
    exit;
  end;
  
  // If there is another instance running, activate it and abort booting new one.
  if Bas_InstanceGetExists() then
  begin
    Bas_InstanceActivate();
    exit;
  end;
  
  // This is the first game instance. If it cannot be initialized, abort booting.
  if Bas_InstanceInitialize() then
    Bas_SessionQuitPush(@Bas_InstanceQuit)
  else
  begin
    // Display a system message box that the game instance failed to initialize.
    exit;
  end;
  
  // This is the first instance and it was initialized, so continue booting.
  // ...
end;

First, we attempt to create a global mutex, and if that fails, we abort the boot process. If the mutex is created successfully, we check whether another instance of the game is already running; if so, we activate it and abort the boot of the new instance. If this is the first instance, we register our own window class, push the instance subsystem’s finalization function onto the stack (this isn’t relevant to the example, ignore it), and continue booting.

The Bas_SessionOpen function simply tries to initialize all subsystems of the game. The result of this function determines whether the booting can continue (next step is loading data from the game files, etc.), or whether the booting must be terminated (due to the detection of another instance or the inability to initialize the required subsystems). Just to be clear.


Activating the window of the running instance

All of the above applies to the subsystem responsible for handling instances, detecting another instance, and sending a custom message to it to activate its window. In order for the game instance to receive this message and respond to it, it must be listening for it. To do this, the game window subsystem registers its own callback as a message hook:

function Bas_WindowOpen (): TBas_Bool8;
begin
  // ...
  
  SDL_SetWindowsMessageHook(@Bas_WindowCallbackMessage, nil);
  
  // ...
end;

Upon receiving our message, this callback restores the window to the screen and brings it into focus:

function Bas_WindowCallbackMessage (userdata: TBas_Pointer; msg: PMSG): TBas_Bool8; cdecl;
begin
  case msg^.message of
    // ...
  
    // Custom message with the request to activate the game window.
    WM_BAS_WINDOW_ACTIVATE:
    begin
      // Restore the window if it is minimized (otherwise do not do this, as the window may be maximized).
      if SDL_GetWindowFlags(Window.Handle) and SDL_WINDOW_MINIMIZED <> 0 then
        SDL_RestoreWindow(Window.Handle);

      // Bring the window to the foreground, focus it and flash the taskbar button.
      SDL_RaiseWindow (Window.Handle);
      SDL_SyncWindow  (Window.Handle);
      SDL_FlashWindow (Window.Handle, SDL_FLASH_BRIEFLY);
    end;
  end;

  Result := True;
end;

This hook is used in my game to listen for many types of messages, so handling a new (custom) message type simply extends its functionality.


Summary

I know this code seems like a lot, but that’s mainly because I copied it from my own project. In reality, the entire implementation for detecting another instance and activating it can easily fit into 20 lines of code (if you write the code in a single block instead of splitting it into multiple functions).

The solution described has been extensively tested (Windows 10 and 11) and works properly. As a kernel object, a mutex guarantees that there is no race condition—and thus ensures the uniqueness of the instance—while activating the game window in the callback ensures that the window is always restored and brought to the foreground.

And since the game window’s class is a GUID string, it’s virtually impossible that the detected window belongs to an application other than our game.

1 Like