godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
88.89k stars 20.16k forks source link

Add process mutex API / option to prevent multiple instances of the game #18150

Closed sprite-1 closed 4 years ago

sprite-1 commented 6 years ago

Is there a way to prevent the game from being launched multiple times? I don't know if this behavior is similar on other platforms but on Windows, I can keep pressing Enter on the *.exe file and it will just keep opening. This might be useful for some people but I would like the option to make it so if the game was launched a 2nd or 3rd time, it will just refocus on the already running instance of the game.

exts commented 6 years ago

I think a option to just check if an game instance is running already to send a OS specific message alert stating the game is already running. Simple, quick and easy for cross-platform. (i think mobile already handles this automatically)

mhilbrunner commented 6 years ago

Nothing built-in to Godot, as far as I know. Needing this too for a current project.

Some keywords for this: interprocess (or cross-process) synchronization, locks/mutexes/semaphores.

There are POSIX and Windows APIs to do this (something like this).

Would be a nice feature. Until Godot offers an cross-platform abstraction for this, you have the following options:

  1. Create a file upon launch, delete upon exit. At start, check if it already exists, if so, exit with error. Note that this is probably the easiest, but can be error-prone on crashes if you don't make sure your lock file is always deleted
  2. Check list of running processes at start, see if your executable is already running (using OS.shell_open()). Note that this is platform-dependant, as you need to call something like ps on Unix and tasklist on Windows. Also error-prone, e.g. tasklist may require permissions or is not available on certain installations. May also be detected by AV heuristics, as both shell usage and listing processes is at least suspicious.
  3. If you're using C# in Godot, or are willing to, you can use a third-party C# library that implements this, like the one I linked above
  4. You could use a third-party library or the Windows/Unix API calls directly to do this to implement a C++ module for Godot
  5. You can use networking; simply create an PacketPeerUDP and listen on a port. If this fails because that specific port is already occupied, it is likely that your game is already running in another process. Note that this can trigger firewall warnings, so if you're not doing multiplayer, may look weird.

Currently I'm doing 5, as its easy and automatically frees the lock (port) on exit/crash and my game is multiplayer anyway.

LinuxUserGD commented 6 years ago

In some cases it could be intended to run multiple instances of one game, e. g. testing multiplayer.

sprite-1 commented 6 years ago

Agreed, it should be optional

SrZorro commented 5 years ago
  1. You can use networking; simply create an PacketPeerUDP and listen on a port. If this fails because that specific port is already occupied, it is likely that your game is already running in another process. Note that this can trigger firewall warnings, so if you're not doing multiplayer, may look weird.

If someone its searching for an implementation for this, here its mine:

Refer to the next comment for an updated version that also works in Windows. (Direct link to update)

# Remember to AutoLoad as first Singleton!
extends Node

# "Port + i" where to check if instance number is already running or not
# be sure that BASE_PORT + [ range of desired instances ] are NOT allocated
# by other services
const BASE_PORT = 2000
const LISTEN_IP = "127.0.0.2"

# This is the variable that will hold the instance number for this game
var GameInstanceNum: int setget noset

func noset(_a):
    assert(false)

var sv # Needed to keep the server alive
func _init():
    sv = TCP_Server.new()
    var port_offset = -1
    while true:
        port_offset += 1
        var port = int(BASE_PORT + port_offset)
        if sv.listen(port, LISTEN_IP) == OK:
            break
    GameInstanceNum = port_offset
SrZorro commented 5 years ago

I found that my implementation don't work correctly in Windows. 127.0.0.2 don't exist in windows but with 127.0.0.1 also doesn't work. I created a gist with two implementations, a port based option for Linux and a file based option for Windows.

In the file based check, it works in Windows by creating a file and keep it opened, so when another instance tries to open that file, it can't because its locked. This way if the game crashes it also releases the look of that instance ID, this implementation somehow only works in Windows and not Linux. So because I just need this to work inside the editor for quick launching two instances, one as a server and all the others as clients, this fix works for me, but I don't recomend to use this implementation for production.

Gist

Calinou commented 4 years ago

Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.

The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.

If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!