Canardia Posted July 21, 2011 Share Posted July 21, 2011 Here is a demo which demonstrates multithreading in LE2. It uses 3 threads plus the main thread: 1) Thread 1 loads models from memory 2) Thread 2 turns them 3) Thread 3 waits until all models are loaded and then gravitates them One model must be loaded once in the main thread, but that is not a big issue as you can reuse them anyway and loading 1 models doesn't take very long, so it's only a small delay when the game starts. After that everything is multithreaded and there is no waiting time while loading new models and levels. Here is the source code: #include "engine.h" #include "pthread.h" #include <vector> #include <string> #define NUMTHREADS 3 #define NUMMODELS 1000 #define LOCK pthread_mutex_lock(&count_mutex); #define UNLOCK pthread_mutex_unlock(&count_mutex); #define WriteText(y,s) SetColor(Vec4(0,0,0,1));DrawText(2,y+1,s.c_str());SetColor(Vec4(1));DrawText(1,y,s.c_str()); using namespace std; pthread_t callThd[NUMTHREADS]; pthread_mutex_t count_mutex; vector<TMesh> model; string s1="Thread 1 Idle",s2="Thread 2 Idle",s3="Thread 3 Idle"; void *LoadModels(void *arg) { LOCK s1="Thread 1 Loading Oildrums"; UNLOCK for(int i=0;i<NUMMODELS;i++) { LOCK BP e=LoadModel("oildrum.gmf"); MoveEntity(e,Vec3(-9+2*(i/10%10),2+1*(i/100),2*(i%10))); TurnEntity(e,Vec3(90,0,0)); SetBodyMass(e,1); EntityType(e,1); SetBodyElasticity(e,2); SetBodyGravityMode(e,0); model.push_back(e); UNLOCK Sleep(20); } LOCK s1="Thread 1 Done"; UNLOCK pthread_exit(NULL); } void *TurnModels(void *arg) { LOCK s2="Thread 2 Turning Oildrums"; UNLOCK bool done=false; while(!done) { LOCK int n=model.size(); UNLOCK for(int i=0;i<n;i++) { LOCK TurnEntity(model.at(i),Vec3(0.1*AppSpeed(),AppSpeed(),0.0*AppSpeed())); UNLOCK } Sleep(1); if(n>=NUMMODELS)done=true; } LOCK s2="Thread 2 Done"; UNLOCK pthread_exit(NULL); } void *GravitateModels(void *arg) { LOCK s3="Thread 3 Waiting for all Oildrums to arrive"; UNLOCK bool done=false; while(!done) { LOCK int n=model.size(); UNLOCK if(n==NUMMODELS) { LOCK s3="Thread 3 Waiting 5 seconds"; UNLOCK Sleep(5000); LOCK s3="Thread 3 Gravitating Oildrums"; UNLOCK for(int i=0;i<n;i++) { LOCK SetBodyGravityMode(model.at(i),1); UNLOCK } done=true; } Sleep(1); } LOCK s3="Thread 3 Done"; UNLOCK pthread_exit(NULL); } int main(int argc, char* argv[]) { Initialize(); Graphics(800,600); pthread_attr_t attr; pthread_mutex_init(&count_mutex,NULL); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); TFramework fw=CreateFramework(); HideEntity(LoadModel("oildrum.gmf")); // initialize FBO for other threads TCamera cam=GetLayerCamera(GetFrameworkLayer(0)); MoveEntity(cam,Vec3(0,5,-10)); TurnEntity(CreateDirectionalLight(),Vec3(45,45,0)); EntityType(CreateTerrain(128),1); SetFarDOF(1); SetDistanceFog(1); SetBackgroundColor(Vec4(0.8,0.8,1,1)); Collisions(); // create other threads pthread_create(&callThd[0],&attr,LoadModels,(void*)1); pthread_create(&callThd[1],&attr,TurnModels,(void*)2); pthread_create(&callThd[2],&attr,GravitateModels,(void*)3); while( !AppTerminate() && !KeyHit() ) { LOCK UpdateFramework(); RenderFramework(); SetBlend(BLEND_ALPHA); WriteText(20,s1); WriteText(40,s2); WriteText(60,s3); SetBlend(BLEND_NONE); UNLOCK Flip(0); Sleep(1); } // kill other threads pthread_detach(callThd[0]); pthread_detach(callThd[1]); pthread_detach(callThd[2]); // wait for other threads to finish pthread_join(callThd[0],NULL); pthread_join(callThd[1],NULL); pthread_join(callThd[2],NULL); // destroy mutex engine pthread_attr_destroy(&attr); pthread_mutex_destroy(&count_mutex); pthread_exit(NULL); return Terminate(); } And here is the complete Code::Blocks project with executable demo also: Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
Rick Posted July 21, 2011 Share Posted July 21, 2011 One model must be loaded once in the main thread, but that is not a big issue as you can reuse them anyway and loading 1 models doesn't take very long, so it's only a small delay when the game starts. After that everything is multithreaded and there is no waiting time while loading new models and levels. So you still have to load unique models in your main thread which still cause pauses to your game? Quote Link to comment Share on other sites More sharing options...
Canardia Posted July 21, 2011 Author Share Posted July 21, 2011 So you still have to load unique models in your main thread which still cause pauses to your game? No, it doesn't pause your game, because it happens only before the game starts, and you can then even do some intro/menu/video in another thread, while the main thread is busy. Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
Rick Posted July 21, 2011 Share Posted July 21, 2011 No, it doesn't pause your game, because it happens only before the game starts, and you can then even do some intro/menu/video in another thread, while the main thread is busy. It only happens at the start if that's how you program your game. Loading hundreds or thousands of unique models at the start of an RPG or MMO isn't practical. Loading them on the fly is preferred to get a streaming world. Weren't you big on streaming worlds without precaching everything at the start? I think that's one of the biggest arguments for multithreading in LE. You would want your main thread to do the manipulation of your models and your secondary thread to be the one that loads them to give you the power of streaming your world. Quote Link to comment Share on other sites More sharing options...
Canardia Posted July 21, 2011 Author Share Posted July 21, 2011 Well do it the other way around: put your game in thread 1, and leave the main thread for loading models then. It's the same effect, except then there is no waiting at all. I'm gonna make a new demo next, which uses that idea Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
Rick Posted July 21, 2011 Share Posted July 21, 2011 Is that really possible? If you try to move a model that isn't fully loaded wouldn't it crash? If you put mutex around wouldn't that pause leaving you with the same issue? I would think you might have to spam your code checking if each model is NULL or not maybe? Quote Link to comment Share on other sites More sharing options...
Canardia Posted July 21, 2011 Author Share Posted July 21, 2011 You can't move models which are not loaded, because they are not in the model vector yet. So everything which is in the model vector is already loaded. Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
Rick Posted July 21, 2011 Share Posted July 21, 2011 Cool. Sounds promising. Any drawbacks at all before we all get excited? Quote Link to comment Share on other sites More sharing options...
Canardia Posted July 21, 2011 Author Share Posted July 21, 2011 I can put the main thread which loads the engine and creates the graphics window to another thread, but the problem remains that the first LoadModel must happen from that thread. Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
VeTaL Posted July 21, 2011 Share Posted July 21, 2011 looking like this is good for instanced models? This reminds me Quote Working on LeaFAQ Link to comment Share on other sites More sharing options...
Canardia Posted July 21, 2011 Author Share Posted July 21, 2011 Well, it's basically good for using the other cores too, instead of just one. Without mutex you can't call any LE2 commands on the other threads. So for example if you have some heavy AI calculations, you can now do them on another thread and still use LE2 commands there. Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
Rekindled Phoenix Posted July 22, 2011 Share Posted July 22, 2011 This is great news! *Goes to make his own multithreaded library Quote Link to comment Share on other sites More sharing options...
Pixel Perfect Posted July 22, 2011 Share Posted July 22, 2011 How expensive is all the locking though? For any process that does a lot without using LE commands I could see this as being advantageous but otherwise will it end up being any more efficient at all? Interesting work by you and Lazlo though! Quote Intel Core i5 2.66 GHz, Asus P7P55D, 8Gb DDR3 RAM, GTX460 1Gb DDR5, Windows 7 (x64), LE Editor, GMax, 3DWS, UU3D Pro, Texture Maker Pro, Shader Map Pro. Development language: C/C++ Link to comment Share on other sites More sharing options...
Canardia Posted July 22, 2011 Author Share Posted July 22, 2011 It's up to you to make the locking so that you don't let time critical parts get sliced by other threads. Normally the locking is just concurrent behaviour, and the CPU does things all the time. When one thing is locked, then it does another, and so an, so there is no time lost in that sense. I don't think the lock/unlock command itself takes much time, of course this can be tested also. And ultimately, you should get number of cores times faster programs, if you use the multithreading in the right way. Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
shadmar Posted July 25, 2011 Share Posted July 25, 2011 Thanks! This is really useful, work as a charm. I've tried this before, but never got the locking correctly. Quote HP Omen - 16GB - i7 - Nvidia GTX 1060 6GB Link to comment Share on other sites More sharing options...
Canardia Posted July 25, 2011 Author Share Posted July 25, 2011 The locking rule is pretty simple. You need to lock always: 1) All LE2 commands, except Flip 2) All variables which are used in other threads too, even if only for read access Don't lock too much, but lock only what is really needed, else you get chunky multithreading and not fluid multithreading. In addition to locking, you need to add at least a Sleep(1) to each thread also, because else the other threads will run very slow and at inconsistent speed, because they are waiting for CPU idle time. Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.