by L. Spiro » Sat Feb 25, 2012 4:13 am
The problem is that you have swapped your render thread and your logic thread. The main thread should be handling logic and input and submitting commands to the render-thread’s command list, while the render thread should be the “other” thread.
I didn’t want to say anything at first when you said the render thread was handling Windows messages, but your last example of being able to change text while a command list is being built brings to light a very common pitfall in the way in which input is handled.
Since it can be handled entirely correctly regardless of how threads are distributed, I will only talk about the main important aspect of input handling for now.
The common pitfall I see is that people will set up callbacks to be called when input is detected. For example, they may pass a function pointer to the input manager and when the input manager sees the B button has been hit, the callback is called and Mario jumps.
The problem is that without specific synchronization with the logical thread, this jump could happen at any time.
What if physics were running while Mario is in mid-air? The physics routine temporarily sets Mario’s isOnGround value to true and makes some checks to prove it is false. Since he is not on the ground it eventually gets set to false after a few rays casts etc. (by the way this is not how the game really works and in reality it would be the opposite way around if it worked anything like this at all, but this is just an example).
Next frame it repeats, sets the value to true, and the thread is interrupted by the B button, which finds Mario on the ground even though he isn’t, and suddenly Mario can air-jump 1/1,000th of the time.
In essence, you are trying to avoid something that can’t be avoided. Input handling must be restricted to the first task your logical loop handles each frame, and if input is coming from another thread then you simply can’t avoid the “mess” of critical sections, regardless of how your rendering is set up.
My old engine, for iOS devices, ran both the logic and rendering on one thread, but it was not the main thread. Input from the main thread was simply logged until the next frame when the logical processing began, at which point it was copied over to a logical-thread buffer for handling. Naturally there were locks and unlocks, but you really don’t need to worry about the performance of this part. Even on the earliest iOS devices there was no impact.
Input that is asynchronous with the logical thread will cause problems not just with text in your labels, but in many other potential places.
It is something you need to fix regardless of your rendering method, and luckily it will implicitly fix the problem of being able to change label texts while the label is submitting render commands, since those 2 events would then be on the same thread.
Your options are to either buffer the input until the logical thread is ready, with a few extra locks that would only stall under rare conditions, or to swap the threads so that input is already on the logical thread from the start. It should still be buffered but it doesn’t have to be copied across threads.
Again, you don’t need to fear having input on another thread and all the locks and copies it involves. The fact is, if that thread is devoted only to input (not also rendering), then you can get much smoother input. Since you won’t be waiting for rendering or physics to get the input from the OS, you can always get them immediately and your timestamps for each event will be more accurate. This will give you better curves with the mouse or touch input for iOS.
L. Spiro
It is amazing how often people try to be unique, and yet they are always trying to make others be like them.
- L. Spiro 2011