Multithreaded scene loading

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
3DModelerMan
Posts: 1691
Joined: Sun May 18, 2008 9:42 pm

Multithreaded scene loading

Post by 3DModelerMan »

I need to be able to load scenes in the background, and I thought of a way I might be able to do it. If I load the scene using a seperate scene manager, then copy the contents over once it's done... wouldn't that be a good way to do it? The only problem I can think of is that copying might be slower than just loading it single threaded. But processing the user data in the seperate thread and creating game actors would probably be faster right?
That would be illogical captain...

My first full game:
http://www.kongregate.com/games/3DModel ... tor#tipjar
aanderse
Posts: 155
Joined: Sun Aug 10, 2008 2:02 pm
Location: Canada

Post by aanderse »

The scene manager uses the video driver to load textures and stuff which is not thread safe, so that won't fly.
It doesn't matter though, because you don't need to multithread it... See the ISceneUserDataSerializer class, just make calls to your main game loop within the callbacks while your scene loads.
3DModelerMan
Posts: 1691
Joined: Sun May 18, 2008 9:42 pm

Post by 3DModelerMan »

So by doing that you kind of fake multithreading? I bet I could also store the user data somewhere and process it in a seperate thread.
That would be illogical captain...

My first full game:
http://www.kongregate.com/games/3DModel ... tor#tipjar
aanderse
Posts: 155
Joined: Sun Aug 10, 2008 2:02 pm
Location: Canada

Post by aanderse »

I guess you could call it faking multithreading, that's one way to think about it.
The main point being that you aren't running a blocking process (loading the level) anymore.
Loading an entire level will take enough time that the rendering is paused and you feel the effect while playing your game, but just loading a single node at a time and then calling the main game loop (which has your irrlicht beginScene, drawAll, and endScene calls) won't cause any pause in rendering so you'll be golden.

Once you get this working you'll realize you really don't need multithreading for level loading.
zerochen
Posts: 273
Joined: Wed Jan 07, 2009 1:17 am
Location: Germany

Post by zerochen »

hi

i wrote a threaded loader some times ago.
it works very fine for me. so you can do other staff like show a loading screen while loading
you only need boost.

CThread.h

Code: Select all

#ifndef CTHREAD_H 
#define CTHREAD_H 

#include <boost/thread/thread.hpp>

class CThread
{
public:
	CThread();
	virtual ~CThread();

	virtual void run() {};

	bool start();
	bool stop();

	volatile bool isRunning() { return threadRun; };

	// as a class member the mutex is used to ensure thread-safety 
	// while accessing any data in this class by another thread
	boost::mutex mutex; 

protected:
	volatile bool threadRun;
	boost::thread* thread;
};

#endif
CThread.cpp

Code: Select all

#include "CThread.h"

CThread::CThread() 
	: threadRun(false) 
{
	thread = nullptr;
}

CThread::~CThread() 
{
	stop();
}

bool CThread::start()
{	
	if(threadRun)
		return false;

	if(thread)
		stop();

	threadRun = true;
	
	thread = new boost::thread(&CThread::run, this); 

	if(!thread)
		threadRun = false;

	return threadRun;
}

bool CThread::stop()
{
	if(!thread) 
		return false;

	threadRun = false; //thread loop ende

	thread->join(); //warten bis thread ende

	//hier keine 2 threads mehr
	
	return true;
}
CLoadManager.h

Code: Select all

#ifndef CLOADMANAGER_H 
#define CLOADMANAGER_H 

#include <ISceneManager.h>
#include <IVideoDriver.h>

#include "CLoadCallback.h"
#include "CThread.h"

using namespace irr;

class CLoadManager : CThread
{
public:
	CLoadManager(CLoadCallback* cb, scene::ISceneManager* smgr);
	~CLoadManager();

	void addMesh(const io::path& filename, const s32 id = -1);
	void addTexture(const io::path& filename, const s32 id = -1);

	virtual void run();

private:
	void add(const io::path& filename, const s32 id, const E_LOAD_EVENT_TYPE type);

	core::list<SLoadEvent> loadList;

	scene::ISceneManager* smgr;

	CLoadCallback* cb;
};

#endif
CLoadManager.cpp

Code: Select all

#include "CLoadManager.h"

CLoadManager::CLoadManager(CLoadCallback* cb, scene::ISceneManager* smgr)
	: cb(cb), smgr(smgr)
{
}

CLoadManager::~CLoadManager()
{
	loadList.clear();
}

void CLoadManager::addMesh(const io::path& filename, const s32 id)
{
	add(filename, id, ELET_MESH);
}

void CLoadManager::addTexture(const io::path& filename, const s32 id)
{
	add(filename, id, ELET_TEXTURE);
}

void CLoadManager::add(const io::path& filename, const s32 id, const E_LOAD_EVENT_TYPE type)
{
	SLoadEvent load;
	load.id = id;
	load.eventType = type;
	load.filename = filename;

	loadList.push_back(load);

	if(!threadRun)
		start();
}

void CLoadManager::run()
{
	while(!loadList.empty())
	{
		core::list<SLoadEvent>::Iterator it = loadList.begin();

		switch(it->eventType)
		{
		case ELET_MESH:
			if(!smgr->getMesh(it->filename))
				it->eventType = ELET_ERROR;
			break;
		case ELET_TEXTURE:
			if(!smgr->getVideoDriver()->getTexture(it->filename))
				it->eventType = ELET_ERROR;
			break;
		}

		cb->loadEvent(*it);

		loadList.erase(it);
	}
	threadRun = false;
}
CLoadCallback.h

Code: Select all

#ifndef CLOADCALLBACK_H 
#define CLOADCALLBACK_H 

#include <IAnimatedMesh.h>

enum E_LOAD_EVENT_TYPE
{
	ELET_TEXTURE,
	ELET_MESH,

	ELET_ERROR
};

struct SLoadEvent
{
	irr::s32 id;
	irr::io::path filename;

	E_LOAD_EVENT_TYPE eventType;
};

class CLoadCallback 
{
public:
	CLoadCallback() {};
	virtual ~CLoadCallback() {};

	virtual void loadEvent(const SLoadEvent& event) {};

private:
};

#endif
i think you can easy expand it
you can use it like this:

Code: Select all

class CMyApp : CLoadCallback
{
public:
	CMyApp(scene::ISceneManager* smgr) : loadManager(this, smgr)
	{}

    void doSomething()
    {
		loadManager.addMesh("ship.3ds", 0);  
		//do other staff while loading     
    }

    virtual void loadEvent(const SLoadEvent& event) 
    {
		switch(event.eventType)
		{
		case ELET_MESH:
       	switch(event.id)
       	{	
       	case 0:
       			//mesh is loaded get it with smgr->getMesh(event.filename);
				return;
			default:
				printf("Mesh loaded '%s' (ID: %d).\n", event.filename, event.id);
				return;
			}
		case ELET_TEXTURE:
			switch(event.id)
			{	
			default:
				printf("Texture loaded '%s' (ID: %d).\n", event.filename, event.id);
				return;
			}
		case ELET_ERROR:
			printf("Failed to load '%s' (ID: %s)\n", event.filename, event.id);
			return;
		}
	}
private:
	CLoadManager loadManager;
}
christianclavet
Posts: 1638
Joined: Mon Apr 30, 2007 3:24 am
Location: Montreal, CANADA
Contact:

Post by christianclavet »

aanderse, is there an example somewhere I could check to use the scene user data serializer? This could be really useful, as we could update the display while loading the scene.
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

christianclavet wrote:aanderse, is there an example somewhere I could check to use the scene user data serializer? This could be really useful, as we could update the display while loading the scene.
It's used in the scene manager:
virtual bool loadScene (io::IReadFile *file, ISceneUserDataSerializer *userDataSerializer=0)=0
Loads a scene. Note that the current scene is not cleared before.
virtual bool loadScene (const io::path &filename, ISceneUserDataSerializer *userDataSerializer=0)=0
Loads a scene. Note that the current scene is not cleared before.

virtual bool saveScene (io::IWriteFile *file, ISceneUserDataSerializer *userDataSerializer=0)=0
Saves the current scene into a file.
virtual bool saveScene (const io::path &filename, ISceneUserDataSerializer *userDataSerializer=0)=0
Saves the current scene into a file.
Though I think Its only useful if you use .irr scenes.
Working on game: Marrbles (Currently stopped).
christianclavet
Posts: 1638
Joined: Mon Apr 30, 2007 3:24 am
Location: Montreal, CANADA
Contact:

Post by christianclavet »

So something like this?:

Code: Select all

#include "irrlicht.h"

virtual void irr::scene::ISceneUserDataSerializer::OnCreateNode  ( ISceneNode *  node  ) 
{
     printf ("Got a node loading!");
}

void main()
{
// init the device, scene manager, etc
...

// Load the IRRscene
    ISceneUserDataSerializer *userDataSerializer = NULL;    
    bool result = LoadScene ("test.irr",userDataSerializer)

// Main loop
...
}
So the code will be called when a scene node is being created in the scene manager while loading a IRR file?

Do we need to initialize an ISCeneUserDataSerializer pointer for doing this?
3DModelerMan
Posts: 1691
Joined: Sun May 18, 2008 9:42 pm

Post by 3DModelerMan »

Yeah, you just implement the ISceneUserDataSerializer interface and pass a pointer to your instance of that interface to the lodascene function.
That would be illogical captain...

My first full game:
http://www.kongregate.com/games/3DModel ... tor#tipjar
aanderse
Posts: 155
Joined: Sun Aug 10, 2008 2:02 pm
Location: Canada

Post by aanderse »

christianclavet

Here is a pretty simple example I just quickly typed up for you:

Code: Select all


class UpdateRenderLoopPlx : public irr::scene::ISceneUserDataSerializer {

    public:

        UpdateRenderLoopPlx (irr::IrrlichtDevice *device) : Device (device) {

            Device->grab ();

        }

        ~UpdateRenderLoopPlx () {

            Device->drop ();

        }

        irr::io::IAttributes *createUserData (irr::scene::ISceneNode *forSceneNode) {

            return 0;

        }

        void OnCreateNode (irr::scene::ISceneNode *node) {

            Device->getVideoDriver()->beginScene (true, true, irr::video::SColor (255, 0, 0, 0));
            Device->getSceneManager()->drawAll ();
            Device->getGUIEnvironment()->drawAll ();
            Device->getVideoDriver()->endScene ();

            // any other stuff you have to do in your main loop, do here

        }

        void OnReadUserData (irr::scene::ISceneNode *forSceneNode, irr::io::IAttributes *userData) {

            return 0;

        }

    private:

        irr::IrrlichtDevice *Device;

};

int main (int argc, char *argv[]) {

    irr::IrrlichtDevice *device = irr::createDevice ();

    if (!device) {

        return 1;

    }

    UpdateRenderLoopPlx loopUpdate (device);

    device->getSceneManager()->loadScene ("myscene.irr", &loopUpdate);

    while (device->run ()) {

        device->getVideoDriver()->beginScene (true, true, irr::video::SColor (255, 0, 0, 0));
        device->getSceneManager()->drawAll ();
        device->getGUIEnvironment()->drawAll ();
        device->getVideoDriver()->endScene ();

        // whenever you want to load the scene just pass the loopUpdate variable as the second param

    }

    return 0;

}
Hope this helps, but if not just post.
Post Reply