开发者

Is Clang more deterministic than GCC across platforms?

开发者 https://www.devze.com 2023-03-19 00:00 出处:网络
I\'m considering the feasibility of programming a multi-user RTS game (partly) in C++. What I quickly discovered, is that one hard requirement is that the game simulation must be fully deterministic t

I'm considering the feasibility of programming a multi-user RTS game (partly) in C++. What I quickly discovered, is that one hard requirement is that the game simulation must be fully deterministic to the very last bit across the server and all clients, to be able to limit the network communication to the user input, and not the game state itself. Since everyone has a different computer, this seems like a hard problem.

So, is there some "magic" way of getting the C++ compiler to create an executable that will be fully deterministic across Linux (the server), Windows and Mac? I think the two main OSS C++ compilers are GCC and Clang, so I was wondering if one performs better than the other in this regard.

I would also be interested in any test-suite that could be used to validate C++ determinis开发者_JAVA百科m.

[EDIT] By deterministic, I meant that the compiled program, given the same initial state, and the input in the same order, will always produce the same output, on any platform where it runs. So, also across the network. Consistent sounds like an appropriate definition of this behavior to me, but I'm not a native speaker, so I might misinterpret the exact meaning.

[EDIT#2] While discussions about whether determinism/consistency matters, and whether I should aim for that in a game engine, and how big a problem it generally is in C++, is quite interesting, it does not in any way actually answer the question. So far, no one had any fact telling me if I should use Clang or GCC to get the most reliable/deterministic/consistent results.

[EDIT#3] It just occurred to me that there IS a way to get exactly the same result in C++ as in Java. One has to take an open source implementation of the JVM, and extract the code that implements the operators and mathematical functions. Then you turn it into a stand-alone library and call inlineable functions in it, instead of using operators directly. It would be a pain to do by hand, but if the code is generated, then it's a perfect solution. Maybe that could even be done with classes and operator overloading, so it looks natural as well.


Since everyone has a different computer, this seems like a hard problem.

It's not. Really, this kind of networking is quite simple, so long as you don't do anything that is undefined by the specification. IEEE-754 is very clear on exactly how floating-point math is to be done, how rounding is to be done, etc, and it is implemented identically across platforms.

The biggest thing you need to not do is rely on SIMD CPU instructions in code that needs to be deterministic (note: this is physics, AI, and such: game state. Not graphics, which is where you need SIMD). These kinds of instructions play fast-and-loose with the floating-point rules. So no SIMD in your game code; only in the "client" code (graphics, sound, etc).

Also, you need to make sure that your game state doesn't depend on things like the time; each game state clock tick should be a fixed time interval, not based on the PC's clock or anything of that nature.

Obviously, you should avoid any random function you don't have the code to. But again, only for your main gameplay loop; the graphics stuff can be client-specific, since it's just visuals and doesn't matter.

That is pretty much it, as far as keeping the two game states in sync. The compiler you use isn't going to be a big issue for you.

Note that StarCraft and StarCraft II use this as the basis of their networking model. They both run on Macs and PCs, and both can play against each other. So it's very possible, and doesn't require Clang.

Though if you like Clang, you should use it. But that should be because you like it, not for networking.


Don't rely on undefined or unspecified behaviour, (particularly, don't use floating-point), and it doesn't matter what compiler you use.

If a is 1, and b is 2, then a + b is 3. This is guaranteed by the language standard.

C++ isn't a land in which in some compilers some things are 'deterministic' and in other compilers they aren't. C++ states some facts (like 1 + 2 == 3) and leaves some things up to the compiler (like order of evaluation of function arguments). If the output of your program only depends on the former (and the user's input), and you use a standards-compliant compiler, then your program will always produce the same output given the same user input.

If the output of your program also depends on (say) the user's operating system, then the program's still deterministic, it's just that the output is now determined by both the user's input and their operating system. If you want the output only dependent on the user's input, it's up to you to ensure that the user's operating system is not a contributing factor to the output of your program. One way to do this is to only rely on behaviour guaranteed by the language standard, and to use a compiler that conforms to that standard.

In summary, all code is deterministic, based on its input. You just have to make sure that the input is made up only of the things you want it to be.


I think which compiler you are using does not matter that much.

Such fully-deterministic approach was used in Doom for example. Instead of random-number generator they were using a fixed "random"-number array. Game time was measured in in-game ticks (which was about 1/30 of a second if I remember).

If you measure everything by in-game mechanics, rather than offloading your work to some standard libraries, which may come in various versions, I believe you should achieve good portability across different machines. Provided, of course, if those machines will be fast enough to run your code!

However, the network communication can create troubles on its own: latencies, drops, etc. Your game should be able to handle delayed messages and, if necessary, resynchronise itself. You might want, for example, send a full (or at least: more verbose) game state from time to time, rather than relying only on the user input.

Think also about possible exploits:

  • Client: I am throwing a grenade
  • Server: You have no grenades
  • Client: I don't care. I am throwing a grenade nevertheless


This is somewhat of a fools errand. Your program will not be "fully deterministic" (whatever that means) "to the very last bit" on a big endian machine versus a little endian machine, nor on a 64 bit machine versus a 32 bit machine versus some other random machine.

Speaking of random, many games have an element of randomness. If you are achieving this by calling the c-standard function rand(), all bets are off.


If you start using floating point all your bets are off. You will run into hard to find/fix problems where you get different values even on the same platform, just by choosing a Intel or AMD cpu.

Lots of runtime libraries have optimized code-paths for different chips. These are all within the spec, but some are slightly more precise than that. This results in subtle roundoff errors that sooner or later accumulate to a difference that may break things.

Your goal should be to get away without 100% determinisim. After all: Does it matter for the player if a opponent is a pixel more to the left than it should? It is not. What is important is, that the little differences between clients and server don't ruin the gameplay.

What the player sees on his screen should look deterministic, so he doesn't feel cheated, but it is in no way required.

The games I've worked on archived this by constantly resynchronizing the game-state of all entities between all clients. We did however almost never sent the entire gamestate but we sent the game state of few objects each frame distributing the job over several seconds.

Just give the objects where it matters most higher priority than other and it will be fine. In a car racing game for example it does not matter to have the exact position of an opponent car if it is far away from you, and it's fine to only update it every 20 seconds or so.

Inbetween these updates just trust that the little round-off errors don't accumulate so much that you run into troubles.

0

精彩评论

暂无评论...
验证码 换一张
取 消