Multi-threaded event queues?

Context: In some games I'd imagine commands can be entered faster than they complete. For instance, a player might enter a movement command, and while the player is moving to that location, enter a command to cast a spell before the movement command has completed.

If that's true, it stands to reason that commands will likely need to queue into a central location from which another service can (asynchronously) pop those commands and service/execute them in the order they were received.

I'm trying to create a bug-free MVP of a system like this, and want to know if I'm on the right track.

Largely, I just don't know much about threads and I hear there are a bunch of gotchas (esp. with resource sharing), so just wanted to get some pointers on how to do this safely:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <queue>
#include <functional>

std::queue<std::string> commandQueue;

bool busy = false;

void executeCommand(std::string command)
{
  std::this_thread::sleep_until(std::chrono::system_clock::now() + std::chrono::seconds(3)); // commands might "delay" the player.
  std::cout << "Command " << command << " done." << std::endl;
}

void doNextCommand()
{
  std::string cmd = commandQueue.front();
  commandQueue.pop();

  executeCommand(cmd);
  if(!commandQueue.empty())
    doNextCommand();

  busy = false;
}

void serviceQueue()
{
  if(busy) return;

  busy = true;

  // I have no idea what I'm doing here.I just think that I need to spin up a worker thread or make this
  // async because it shouldn't block further command input
  std::thread([](){ doNextCommand(); }).detach();
};

void commandQueueChanged()
{
  serviceQueue();
}


int main()
{
  while(true) {
    std::cout << "Enter a command to fire" << std::endl;
    std::string enterLine;
    getline(std::cin, enterLine);

    commandQueue.push(enterLine);
    commandQueueChanged(); // Some event will fire that notifies the command servicer to start servicing commands.
  };
  return 0;
};


The way I understand it, we have 2 threads accessing the queue. One adding and one servicing. That feels, to me, like it's going to cause a problem, but I'm not sure.

Is there a better way to do this, or a resource you've read that might help me build something a bit more robust?

This should be a running program: g++ main.cpp -std=c++20

Thank you!
Last edited on
What you are describing is far above a beginner's level of coding. I suggest moving this topic to the General Programming forum.

With that said, how to achieve what you want is dependent on the style of game, command-line or GUI, and the OS.

Plus, multi-player would likely require some form of network connectivity that the processing would need to take into account latency of signals.

Are you planning to set up a client/server game architecture?
Hi George,

Thanks for your reply. Sorry about the miscategorization.

The sample program I supplied here is just an abstraction of a game I'm currently working on that uses boost::asio for its server/connection architecture, and boost::signals for an eventbus that passes events across distributed services.

The game is going to be quite simple. Just a text based MUD running on MacOS where people would connect via telnet to the aforementioned server, enter commands, and get text feedback in return. So, along this example, a player might type:

"go north", "take sword", "kill goblin".

We'd queue each of these commands, the servicer would get notified, and immediately start servicing the queue (like above).

I hope I've clarified a bit for you. Thanks again for your response.
Maybe have a look the Producer–Consumer pattern:
https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem

Think of the "producer" as the thread that generates new tasks and puts them into the buffer (queue), provided that there is at least one free slot available. And think of the "consumer" as the thread that continuously takes pending tasks from the buffer – possible waiting for another task to become available, if there currently is none – and then executes them. There could be multiple "consumers", of course.

This is usually implemented with two semaphores, one to count the "free" slots in the buffer and one to count the "pending" tasks in the buffer, plus a mutex to synchronize the access to the "shared" buffer (queue).

(The Wiki article has an example implementation using C++)
Last edited on
@Kigar This is precisely the problem I'm facing. Plus, the C++ implementation you reference in the article is super slick and surprisingly really small and understandable!

Thank you! I'll play around with this pattern and see if I can't adopt it to my needs.

x
If'n you plan on using nothing not available in the C++ standard libraries, C++20 is a good choice* or cross-platform 3rd party like Boost there is no reason why you need to restrict the game to MacOS.

As long as you have access to *nix and/or Windows PCs both have compilers/IDEs that could create executables so your game has only to scale up or down in real time for the number of users currently playing.

*some parts of Boost have/had issues with C+20, the new spaceship comparison <=> (and related comparison operators) was what I remember being one of the problems, at least that was a version or two ago. They may have fixed that by now, I haven't checked beyond installing the latest Boost library version (1.7.9.0).

It appears that 1.7.5.0 was the last version having the C++20 problem, newer versions were bug-fixed.
Topic archived. No new replies allowed.