T3D : passing parameters from a thread to T3D main thread with SimEvent
by Nicolas Buquet · in Torque 3D Professional · 01/19/2010 (1:50 pm) · 21 replies
Hi,
I integrate a libray in T3D.
This library run on a thread.
I got it running along T3D main thread.
I see some real time result in the output window of Visual Studio.
But the communication between this thread and T3D main thread seems to be chaotic and unreliable. I call some Con::printf("...") from my library thread when an event occurred, but this Con::printf call is in general ignored by T3D (no output in the console in 90% of the calls).
So, what is the right method to launch a thread correctly in T3D and to make it communicate with the engine ?
My thread is actually launched as this :
I'm totally ignorant about threads.
Can someone help me ? (René perhaps)
Nicolas Buquet
www.buquet-net.com/cv/
I integrate a libray in T3D.
This library run on a thread.
I got it running along T3D main thread.
I see some real time result in the output window of Visual Studio.
But the communication between this thread and T3D main thread seems to be chaotic and unreliable. I call some Con::printf("...") from my library thread when an event occurred, but this Con::printf call is in general ignored by T3D (no output in the console in 90% of the calls).
So, what is the right method to launch a thread correctly in T3D and to make it communicate with the engine ?
My thread is actually launched as this :
HANDLE myThreadVar;
unsigned myThreadID;
myThreadVar = (HANDLE)_beginthreadex(NULL, 0, &myThread, NULL, 0, &myThreadID);
unsigned __stdcall mThread (void* param)
{
while(myGlobalCondition)
{
makeMyProcessInTheThread();
}
return 0;
}I'm totally ignorant about threads.
Can someone help me ? (René perhaps)
Nicolas Buquet
www.buquet-net.com/cv/
About the author
Recent Threads
#2
01/19/2010 (4:02 pm)
Ah, maybe you can describe what library you are integrating and/or what kind of work the threaded code is meant to do. This will help me/us help you a little better.
#3
The library I implement is wiiYourself (wiiyourself.gl.tter.org/), a wiimote implementation for Windows.
I implemented it in TGE 1.52 (2 years ago) and was accessing the events by getting them from Torque, but here I want to implement it in a cleaner way, because this old way miss the greater part of the events (buttons up/down, accelerations…) in T3D.
So, the wiimote thread call callbacks functions in Torque. I get it running with the wiiYourself exemple (an exe in a DOS window) this way, but when I try it in Troque 3D, the same thing happens as before : 90% of the events (that are fired in the wiimote thread because I see them in the output window) don't reach Torque (some of them arrived).
Torque main thread and the wiimote thread don't need to share any variable. The wiimote thread will fire callback in Torque thread that will insert event in the actionMap manager.
It works very well on Mac this way (callbacks) with another framework (darwinmote), but thread programming on Windows seems a bit more dark to start.
Thanks for your help (if you can).
I was thinking of you for this subject because of your recent intervention in XML-RPC subject.
And thanks for your great work on SFX layer! :-)
Nicolas Buquet
www.buquet-net.com/cv/
01/19/2010 (5:45 pm)
Thanks René, I will have a look at that tomorrow.The library I implement is wiiYourself (wiiyourself.gl.tter.org/), a wiimote implementation for Windows.
I implemented it in TGE 1.52 (2 years ago) and was accessing the events by getting them from Torque, but here I want to implement it in a cleaner way, because this old way miss the greater part of the events (buttons up/down, accelerations…) in T3D.
So, the wiimote thread call callbacks functions in Torque. I get it running with the wiiYourself exemple (an exe in a DOS window) this way, but when I try it in Troque 3D, the same thing happens as before : 90% of the events (that are fired in the wiimote thread because I see them in the output window) don't reach Torque (some of them arrived).
Torque main thread and the wiimote thread don't need to share any variable. The wiimote thread will fire callback in Torque thread that will insert event in the actionMap manager.
It works very well on Mac this way (callbacks) with another framework (darwinmote), but thread programming on Windows seems a bit more dark to start.
Thanks for your help (if you can).
I was thinking of you for this subject because of your recent intervention in XML-RPC subject.
And thanks for your great work on SFX layer! :-)
Nicolas Buquet
www.buquet-net.com/cv/
#4
Directly on the wiimote thread? What functionality in Torque are these functions accessing?
How do you get the main thread to fire the events?
In general, SimEvent is the right way to inject things into the event system. If none of the existing SimEvents fit, you can derive your own SimEvent and implement process(). Injecting SimEvents from threads is safe.
Losing a good share of your data hints at a solid race condition.
As soon as your thread accesses any of the Torque engine state, your threads start sharing data. Most of Torque's engine data cannot and should not be shared with threads in a safe way.
NB: Thanks for the compliment :)
01/20/2010 (4:47 am)
Quote:the wiimote thread call callbacks functions in Torque
Directly on the wiimote thread? What functionality in Torque are these functions accessing?
Quote:The wiimote thread will fire callback in Torque thread that will insert event in the actionMap manager.
How do you get the main thread to fire the events?
In general, SimEvent is the right way to inject things into the event system. If none of the existing SimEvents fit, you can derive your own SimEvent and implement process(). Injecting SimEvents from threads is safe.
Losing a good share of your data hints at a solid race condition.
As soon as your thread accesses any of the Torque engine state, your threads start sharing data. Most of Torque's engine data cannot and should not be shared with threads in a safe way.
NB: Thanks for the compliment :)
#5
Secondly, to implement that, I made a library (as it is how things seems to be cleanly implemented now in T3D to work in plugin mode I suppose). This library connect to the wiimote, handles its properties, and fires the callbacks.
I implemented a wiiManager in T3D (on the main thread) that starts the wii library thread and register its callback (just one). The callback is a global function in T3D.
When the callback is fired (a Wiimote event occurred), it dispatches the event to a specialized global function (acceleration, orientation, buttons, connection/deconnection, extension…).
These specialized events call another global function in a WiiTorqueInterface module that will generate a WiiInputEvent.
Then the wiiEvent is injected in ActionMap::handleEvent as a regular event.
I defined my own WiiEvents in event.h/InputObjectInstances and in actionMlap.cpp/gVirtualMap.
And in ActionMap::processAction, if the event is a wiiEvent, I jump to my own ActionMap::processWiiAction.
I think it is a clean architecture (I have the same structure on Windows and Mac, with the same wiiToruqeinterface files. The wiiManagers differs a bit).
But I'm willing to learn a bit of information about threads in Torque to understand how to make 2 threads communicate easily and with reliability in T3D if you can take time to explain.
Thanks in advance for sharing your knowledge.
Nicolas Buquet
www.buquet-net.com/cv/
01/20/2010 (6:34 am)
First, it seems to work without modification : only the Con::printf() were missing.Secondly, to implement that, I made a library (as it is how things seems to be cleanly implemented now in T3D to work in plugin mode I suppose). This library connect to the wiimote, handles its properties, and fires the callbacks.
I implemented a wiiManager in T3D (on the main thread) that starts the wii library thread and register its callback (just one). The callback is a global function in T3D.
When the callback is fired (a Wiimote event occurred), it dispatches the event to a specialized global function (acceleration, orientation, buttons, connection/deconnection, extension…).
These specialized events call another global function in a WiiTorqueInterface module that will generate a WiiInputEvent.
Then the wiiEvent is injected in ActionMap::handleEvent as a regular event.
I defined my own WiiEvents in event.h/InputObjectInstances and in actionMlap.cpp/gVirtualMap.
And in ActionMap::processAction, if the event is a wiiEvent, I jump to my own ActionMap::processWiiAction.
I think it is a clean architecture (I have the same structure on Windows and Mac, with the same wiiToruqeinterface files. The wiiManagers differs a bit).
But I'm willing to learn a bit of information about threads in Torque to understand how to make 2 threads communicate easily and with reliability in T3D if you can take time to explain.
Thanks in advance for sharing your knowledge.
Nicolas Buquet
www.buquet-net.com/cv/
#6
As far as I understand, the Wii event fires on the thread within the library and then invokes the callback on that thread. This callback receives all the event details and needs to get the main thread to execute the ActionMap code to handle the event.
At this point, there needs to be thread-safe communication between the library thread and the main thread. The callback cannot directly construct an input event and send it to the ActionMap as that *must* happen on the main thread.
How do you handle this particular part?
01/20/2010 (6:47 am)
It's still not quite clear to me how the reaction to an event occurring on the thread is implemented--which is the critical thing here.As far as I understand, the Wii event fires on the thread within the library and then invokes the callback on that thread. This callback receives all the event details and needs to get the main thread to execute the ActionMap code to handle the event.
At this point, there needs to be thread-safe communication between the library thread and the main thread. The callback cannot directly construct an input event and send it to the ActionMap as that *must* happen on the main thread.
How do you handle this particular part?
#7
The search function on T3D side :
The Wii Discovered function on T3D side :
The thread creation on the wiimote library side, launched when the library connects to the wiimote :
The callback function that is on T3D side, but is called from the wiimote library thread (see above) :
01/20/2010 (7:28 am)
The search wiimote thread laucnhed from T3D :wiimoteSearchThreadVar = (HANDLE)_beginthreadex(NULL, 0, &wiimoteSearchThread, NULL, 0, &wiimoteSearchThreadID);
The search function on T3D side :
unsigned __stdcall wiimoteSearchThread (void* param)
{
while(wiimoteEnabled)
{
if( !wiimoteSearchActivated )
{
Sleep(1);
continue;
}
if( currentRemote == NULL ) continue;
if( !currentRemote->Connect(wiimote::FIRST_AVAILABLE ) )
continue;
WiiRemoteDiscovered( currentRemote );
}
return 0;
}The Wii Discovered function on T3D side :
void WiiRemoteDiscovered( wiimote *wii)
{
wiiRemoteIndex = getNextFreeWiiRemoteIndex();
wii->setIndex( wiiRemoteIndex );
wiiRemote[ wiiRemoteIndex ] = wii;
// simple callback example (we use polling for almost everything here):
wii->ChangedCallback = on_state_change;
// notify us only when something related to the extension changes
wii->CallbackTriggerFlags = state_change_flags)( WIIMOTE_CHANGED | NUNCHUK_CHANGED | CLASSIC_CHANGED | EXTENSION_CHANGED );
wiiSearch(0);
wiiConnected( wiiRemoteIndex, 0 );
expansionPortChanged( wii );
}The thread creation on the wiimote library side, launched when the library connects to the wiimote :
// launch the completion wait/callback thread
if(!ReadParseThread)
{
ReadParseThread = (HANDLE)_beginthreadex(NULL, 0, ReadParseThreadfunc, this, 0, NULL);
DEEP_TRACE(_T(".... creating read thread"));
_ASSERT(ReadParseThread);
if(!ReadParseThread)
return false;
SetThreadPriority(ReadParseThread, WORKER_THREAD_PRIORITY);
}The callback function that is on T3D side, but is called from the wiimote library thread (see above) :
void on_state_change (wiimote &remote, state_change_flags changed)
{...}
#8
Hehe, the interesting part is on_state_change.
On which thread is this called? Since you're polling on the search thread, I assume another thread serves the asynchronous events.
And from there, how do the events get from on_state_change into Torque? Is it calling directly into the Torque event code from the thread? If so, it shouldn't do that. It should use SimEvents.
01/20/2010 (7:37 am)
Hehe, the interesting part is on_state_change.
On which thread is this called? Since you're polling on the search thread, I assume another thread serves the asynchronous events.
And from there, how do the events get from on_state_change into Torque? Is it calling directly into the Torque event code from the thread? If so, it shouldn't do that. It should use SimEvents.
#9
The on_state_change is called from the wiimote library thread, but it is a global function on T3D side.
The asynchronous event are served from the wiimote library.
The on_state_change (in my WiiManager on T3D side) call in my WiiTorqueInterface (on T3D side) that generate the inputEvent and make the call to plug into Torque ActionMap with ActionMap::handleEvent().
So the thread jump is from the wiimote library (in wiimote thread) calling the on_state_change that is a global function on T3D side.
01/20/2010 (7:44 am)
I was updating my previous post for more information and formatting.The on_state_change is called from the wiimote library thread, but it is a global function on T3D side.
The asynchronous event are served from the wiimote library.
The on_state_change (in my WiiManager on T3D side) call in my WiiTorqueInterface (on T3D side) that generate the inputEvent and make the call to plug into Torque ActionMap with ActionMap::handleEvent().
So the thread jump is from the wiimote library (in wiimote thread) calling the on_state_change that is a global function on T3D side.
#10
Ok, think we're slowly getting to the bottom of it.
There actually is no thread jump from the wiimote library to the T3D main thread. The callback from Wiimote happens on its private threads so in order to communicate from that thread to the T3D main thread, you need to use thread-safe constructs.
Calling from on_state_change (directly or indirectly) into ActionMap or other engine subsystems will cause data loss/corruption and/or crashes.
The proper way to do it is to have on_state_change feed a SimEvent into the T3D event queue that is then picked up and processed by the main thread.
01/20/2010 (7:54 am)
Ok, think we're slowly getting to the bottom of it.
There actually is no thread jump from the wiimote library to the T3D main thread. The callback from Wiimote happens on its private threads so in order to communicate from that thread to the T3D main thread, you need to use thread-safe constructs.
Calling from on_state_change (directly or indirectly) into ActionMap or other engine subsystems will cause data loss/corruption and/or crashes.
The proper way to do it is to have on_state_change feed a SimEvent into the T3D event queue that is then picked up and processed by the main thread.
#11
Even if the on_state_change global function is compiled in the 'engine' section (the main DLL) of the project ?
It is a global function in the same way as version.cpp/getVersionNumber().
01/20/2010 (9:41 am)
Quote:
Calling from on_state_change (directly or indirectly) into ActionMap or other engine subsystems will cause data loss/corruption and/or crashes.
Even if the on_state_change global function is compiled in the 'engine' section (the main DLL) of the project ?
It is a global function in the same way as version.cpp/getVersionNumber().
#12
To explain a little: to execution, it doesn't matter where a function is located. It only matters on which execution context on the CPU it gets called.
So if Wiimote detects an event on its event thread and then invokes the callback, all that happens within the execution context of that thread, i.e. that callback is executed on that thread. When control flow reaches on_state_change and it passes flow on into the engine, that still stays on that thread.
This is a very low-level thing that is not at all evident in source code. Whoever calls a function defines the execution context.
So, in practice that means that because on_state_change is not running on Torque's engine thread, you cannot call into or use most of the engine functionality.
Still, this is easy to solve. Construct a custom SimEvent that captures the event data you need and implement the event trigger logic in process(). Then queue that event from on_state_change. The engine will pick this up in its main loop and execute process() on its engine thread. Thus you can safely call into engine code (e.g. ActionMap, Console, etc.) from there.
01/20/2010 (9:48 am)
To explain a little: to execution, it doesn't matter where a function is located. It only matters on which execution context on the CPU it gets called.
So if Wiimote detects an event on its event thread and then invokes the callback, all that happens within the execution context of that thread, i.e. that callback is executed on that thread. When control flow reaches on_state_change and it passes flow on into the engine, that still stays on that thread.
This is a very low-level thing that is not at all evident in source code. Whoever calls a function defines the execution context.
So, in practice that means that because on_state_change is not running on Torque's engine thread, you cannot call into or use most of the engine functionality.
Still, this is easy to solve. Construct a custom SimEvent that captures the event data you need and implement the event trigger logic in process(). Then queue that event from on_state_change. The engine will pick this up in its main loop and execute process() on its engine thread. Thus you can safely call into engine code (e.g. ActionMap, Console, etc.) from there.
#13
If I understand correctly, on_state_change (that belong to the wiimote thread because the wiimote thread is the initial caller in the call stack) will generate a simEvent.
This simEvent will be in a shared memory (the heap) accessible from all thread (or at least T3D main thread).
I think I queue the event in simManager/gEventQueue with postEvent (I will dig to understand the parameters).
Then, T3D mainloop will process gEventQueue as usual.
Did I get it ?
What will be the typical delay between my event capture and my event processing (a few ms or 100-200 ms) ?
01/20/2010 (10:10 am)
So that will be a little different than what happens with a mouse or keyboard event (that are in T3D main thread : that's the difference in structure).If I understand correctly, on_state_change (that belong to the wiimote thread because the wiimote thread is the initial caller in the call stack) will generate a simEvent.
This simEvent will be in a shared memory (the heap) accessible from all thread (or at least T3D main thread).
I think I queue the event in simManager/gEventQueue with postEvent (I will dig to understand the parameters).
Then, T3D mainloop will process gEventQueue as usual.
Did I get it ?
What will be the typical delay between my event capture and my event processing (a few ms or 100-200 ms) ?
#14
SimEvents are by far the easiest means for you to hook into this.
The SimEvent is allocated on the global (shared between threads) C++ runtime heap and automatically deleted by the main loop code once the event has been processed.
Simply derive a class from SimEvent, put your relevant Wiimote event data in it, and implement a process() method that does what previously on_state_change() did.
To queue the event, call postEvent like this
The rest happens automatically. Your process() method gets called on the main thread and the event is deleted then.
The absolute delay time will depend on how the threads are being run. Single core/processor? What other threads are running? etc.
Usually, though, the main thread will basically hog one CPU/core 100% and will immediately pick this up in its event loop which runs at a high frequency.
01/20/2010 (10:24 am)
Yep, the message pump for keyboard, mouse, window, etc. events is on the main thread so the event code there can immediately call into the system (however, this is still using a queue so events don't get immediately handled either). Torque's main loop simply hooks onto this by periodically injecting a time event that will trigger the main loop code.SimEvents are by far the easiest means for you to hook into this.
The SimEvent is allocated on the global (shared between threads) C++ runtime heap and automatically deleted by the main loop code once the event has been processed.
Simply derive a class from SimEvent, put your relevant Wiimote event data in it, and implement a process() method that does what previously on_state_change() did.
To queue the event, call postEvent like this
Sim::postEvent( Sim::getRootGroup(), myWiimoteEvent, Sim::getCurrentTime() + 1 );
The rest happens automatically. Your process() method gets called on the main thread and the event is deleted then.
The absolute delay time will depend on how the threads are being run. Single core/processor? What other threads are running? etc.
Usually, though, the main thread will basically hog one CPU/core 100% and will immediately pick this up in its event loop which runs at a high frequency.
#15
BTW, an alternative would be to actually feed the application main loop with input events for your device. The downside to this is that it is platform-dependent and requires a lot more code than this solution here.
01/20/2010 (10:27 am)
BTW, an alternative would be to actually feed the application main loop with input events for your device. The downside to this is that it is platform-dependent and requires a lot more code than this solution here.
#16
I will implement it this way.
Thank you very much for your patience and to have take time to answer my questions. That was an interesting discussion. I will learn more about SimEvent this way, and learn more about threads by continuing to cruise the forums.
Have a good afternoon in Tuebingen.
Nicolas Buquet
www.buquet-net.com/cv/
01/20/2010 (10:51 am)
Ok.I will implement it this way.
Thank you very much for your patience and to have take time to answer my questions. That was an interesting discussion. I will learn more about SimEvent this way, and learn more about threads by continuing to cruise the forums.
Have a good afternoon in Tuebingen.
Nicolas Buquet
www.buquet-net.com/cv/
#17
You too have a good afternoon in... wait... Paris?? Man, always wanted to go to Paris! :)
01/20/2010 (10:57 am)
You're welcome. Enjoying this, too.You too have a good afternoon in... wait... Paris?? Man, always wanted to go to Paris! :)
#18
I live near Tours, in a village, 250 km away from Paris, in the west of France (called the Center).
If you're in the area at any moment, you're welcome at home of course.
01/20/2010 (11:50 am)
I have an office in Paris, but I don't live there now.I live near Tours, in a village, 250 km away from Paris, in the west of France (called the Center).
If you're in the area at any moment, you're welcome at home of course.
#19
So, after measuring that with Platform::getVirtualMilliSeconds() method, if the simulation runs at 25 fps, the delay is 40 ms.
When my simulation runs at 50 fps, the delay goes down to 20 ms.
It is the same value as the mspf (milliseconds per frame) shown with "metrics(fps)".
By the way Rene, the simEvent were in fact easy to implement, and they arrive correctly to the main thread. You enlightment were precious.
01/21/2010 (11:43 am)
About the typical delay between the simEvent generation and the simEvent processing, it is exactly tied to the current framerate (and it is logical).So, after measuring that with Platform::getVirtualMilliSeconds() method, if the simulation runs at 25 fps, the delay is 40 ms.
When my simulation runs at 50 fps, the delay goes down to 20 ms.
It is the same value as the mspf (milliseconds per frame) shown with "metrics(fps)".
By the way Rene, the simEvent were in fact easy to implement, and they arrive correctly to the main thread. You enlightment were precious.
#20
Great. Gad it's working for you.
BTW, even if your event handling would happen at a higher frequency it probably wouldn't make a difference as I assume the Wiimote triggers changes in the sim logic--which runs at a fixed timestep of 32ms.
01/21/2010 (1:07 pm)
Great. Gad it's working for you.
BTW, even if your event handling would happen at a higher frequency it probably wouldn't make a difference as I assume the Wiimote triggers changes in the sim logic--which runs at a fixed timestep of 32ms.
Associate Rene Damm
Rather than using the Win32 API, better use the threading infrastructure that's already present.
Simply derive a class from Thread and implement your logic in the run() method.
The console is not thread-safe so using Con::printf from a thread is not good. To output debug messages, use Platform::outputDebugString.
Be careful when accessing any engine structures. Usually it's best to let two threads communicate through dedicated interface data structures that are concurrently reference-counted from both threads.
Also, if the work that is being done on a thread is a series of jobs rather than continuous work, it is often easier to use work items issued to the thread pool.