5 minutes ago, silikone said:
Usually, an engine has to strike a balance between these three factors, sacrificing at least one to maximize another.
I wouldn't agree with that statement. They're all independent concerns.
Every modern console game is multi-threaded. My workstation has roughly the same CPU model as the XbOne/PS4, except that the Consoles are clocked around 1.6GHz, while my PC is clocked at 4GHz. The consoles are slowwwwwwwwww... but they have 8 cores. So you have to write your code so that it will actually run across 8 cores. The PS3/Xb360 were the first platforms to force this change in mindset, so multi-threaded games have been a thing for about a decade now.
By latency I assume you mean the latency between the user physically pressing a button and the monitor showing some change as a result of that button press -- input-to-photon latency.
In a typical game this will be about 3-4 frames, or 2-3 on a CRT/projector... In the best possible situation it looks something like:
* user presses button, the game will poll for the button state at the beginning of the next frame. This will be somewhere from 0 to 1 frames from now (+0.5 frames on average).
* game starts a new frame, polls the input devices, updates game state (+1 frame)
* game finishes the frame by issuing the rendering commands to the GPU.
* GPU then spends an entire frame executing these rendering commands (+1 frame).
* Your nice LCD monitor then decides to buffer the image for about a frame before displaying it (+1 frame)
At 60Hz, that's around 50-60ms. The easiest was to reduce those numbers is to just run at a higher framerate. If you can run the loop at 200Hz (no vsync) then the latency will be <20ms.
Games may make tradeoffs that produce worse latency than this, but not because of multi-threading/determinism. A common one is using a fixed-time-step for your gameplay update loop, in order to keep the physics more stable... actually yeah, stable physics is a determinism concern, so you're right there! In order to use a fixed-time-step and keep the visual animation perfectly smooth you have three choices:
1) use a very small time-step (like 1000Hz) where the jittery movement won't be noticed.
2) buffer an extra frame of game-state and interpolate object positions.
3) extrapolate object positions instead of interpolating, though this still causes jittery movement when objects change direction/collide/etc..
Choice #2 adds another frame to the above calculations.
Another reason to add an extra frame of latency is if input rhythm / relative timing of inputs is important. To get perfect rhythm, you can poll the input device very fast (e.g. at 1000Hz) and push all the inputs into a queue with a timestamp of when they were recorded. The game can buffer up a whole frame's worth of inputs in this manner, and then in the gameplay logic it can process the most recent frame's worth (e.g. a 16.67ms slice of inputs) at once, taking the timestamps into account. You're then able to process inputs with sub-frame accuracy -- e.g. even though you still receive all inputs right at the start of an update cycle, you can determine facts such as- the player pushed this button 3/4ths of the way through the frame.
Determinism is often down to our desire for fast floating point math. The IEEE has a specification on how floats should behave, and you can tell your compiler to follow that specification to the letter, and then you know that your calculations are reproducible... However, this is often a hell of a lot slower than telling your compiler to ignore the IEEE specification. Then there's loads of other things like being very careful how you generate random numbers, and very careful about when and how any kind of user input is allowed to affect the simulation. e.g. in a peer-to-peer RTS game, you might need to transmit everyone's user-inputs to every other player first, get acknowledgement, then apply everyone's user inputs simultaneously on an agreed upon simulation frame. Ok... that's also a case where determinism does necessitate higher input latency I'm getting your post now! However, in that situation, the local client can start playing sound effects and animations immediately, which makes it seem like there's no input latency, even though they're not allowed to actually modify the simulation state for another 500ms.
There are situations where certain multi-threading approaches can introduce non-determinism, but all of those approaches are wrong. If you're multi-threading your game in such a way where your support for threading means that the game is no longer deterministic, then you're doing something terribly wrong and need to turn around and go back. I can't stress that one enough. There's no reason that multi-threaded gameplay code shouldn't behave exactly the same as a single-threaded version.