Asynchronous Asset Loading (data streaming)

Started by
16 comments, last by mokaschitta 13 years, 5 months ago
Quote:Original post by phantom
A simple solution would be;
- On requesting the data I would pass back a handle which has a bool you can query for 'ready'ness.
- Renderer/main thread queries this value when it goes to use it
- Loader set to 'true' when the data is ready


This is a case where using a memory barrier is essential. This page has a good example of why under Fixing a Race Condition.

If you use InterlockedExchange to set the bool to true then that implicitly creates a memory barrier, whereas using assignment won't. Additionally please don't use the 'volatile' keyword to try and fix as it creates a false sense of security and may not have the same effect on non MS compilers.
Advertisement
Hmm,

It sounds to me like this is the wrong method to address the problem. Not the sync behavior using atomic operations but the whole concept presented here.

I admit I haven't implemented such a scheme but wouldn't it be easier if the loader thread would load a portion of the scene graph, along with its assets and once its loaded, either it or the master thread (it depends on the design) will attach the completed scene subtree to the current scene graph.

Attaching is as simple as assignment of parent/child, possible in several places but still. Connecting and disconnecting entire subtrees under a lock every once in a while is perfectly viable and shouldn't incur any performance problems.

The concept presented above, to my understanding, means the following:

1. I have an entire scene graph
2. In order to conserve and recycle memory I'm going to offload and load textures and other big resources based on several criteria.
3. All nodes that need access to assets then must be able to query a resource manager to verify their assets are loaded and if not, request loading.

This method seems more difficult. Not only to code properly but to debug. Not just because of threading problems but we also introduced loading states and when an asset it loaded it is held by the graph node (albeit with a handle of some sort).

Now consider that you have specific unloading criteria and when you unload you probably unload entire chunks of resources that fit entire subtrees in your scene graph so it boils down to something along the line of what I suggested earlier but with a lot more code overhead.


I've done streaming of other things, not a scene graph, but that seems to be a better solution to me.
-----------------------------He moves in space with minimum waste and maximum joyGalactic Conflict demo reel -http://www.youtube.com/watch?v=hh8z5jdpfXY
I'll definitely look into memory barriers and returning handles upon asset requests. I also find loading a whole portion of the scene graph interesting. I appreciate everyone posting their thoughts on this, I'll post any results as soon as I change my implementation. That being said, I encourage everyone to keep posting thoughts if you feel like it.

Thanks again!


P.S.

@voguemaster

Quote:I've done streaming of other things


What have you streamed before?
I've worked on projects that stream medical images and do so with prioritization that is dynamic. Its a medical viewing application and memory constraints also dictate how you unload images.

That's in its simple form of course :) technically the data structures more likely resemble several trees when you manage them. Only in the final rendering you actual present images in a "list" of some sort.
-----------------------------He moves in space with minimum waste and maximum joyGalactic Conflict demo reel -http://www.youtube.com/watch?v=hh8z5jdpfXY
Some great replies here so far. One suggestion I wanted to add is avoid doing look ups into your data manager by string. This pattern never scales (huge performance hit with a large catalog), uses a lot of memory, and often causes fragmentation. Instead, I would generate a hash of your asset names (32bit or 64bit, possibly working in a bucketed hash to handle collisions) and make your requests against hash values instead.

Cheers!
Graham
Heck, I'd like to work in bioware too :). Can't say enough good words about their games. Kudos.
-----------------------------He moves in space with minimum waste and maximum joyGalactic Conflict demo reel -http://www.youtube.com/watch?v=hh8z5jdpfXY
I've made an asynchronous loading system similiar to what you want.

I use a task system, that keeps some threads (you can control how many you want) in a pool, waiting for tasks, and executes them in the background. I always use thread-safe message queues to communicate between the threads and the queue blocks when it doesn't have anything, so the threads only wake up when there's anything to do.

I also have a ResourceManager class, that the rest of the engine uses to request resources. If they are not already cached, then I create a new task which I submit to the task system. When the background thread completes the loading, it queues a message in the ResourceManager saying the loading finished, and once that happens the Resource itself is changed to the Loaded state, and I notify whoever subscribed to the onResourceLoaded event.

It currently returns reference counting pointers. These are safe for multithreaded use by using atomic operations, the InterlockedExchange functions that were talked about earlier. I'm currently thinking of switching to a handle-based system, as pointers makes it harder to support resource reloading at runtime. Currently I need to notify everything that there the pointer of a resource changed.

Here is the code where the magic happens, hope it helps.

Task Manager
Concurrent Queue
Resource Manager
Resource Loader
Resource Task

I just started working on a threaded jobQueue aswell, and I came across a gamedev thread were Hodgeman proposed the idea of using double buffering for the jobQueue (vector list or whatever you prefer). Basically that way you would have only one list you would add jobs to each frame, and another one that get's read from, you would only have to use a mutex when swapping the buffers. (that idea requires that each subthread is using it's own queue though. basically my manager class simply tries to equally distribute the work over all subthread queues). Anyways it has the advantage that you don't have to get into lockless queues which are hard to get right and only have one mutex lock for each thread per frame, instead of each time you add and read from it.
And as triton points out thread blocking when no task a there is a great win.
Currently I am doing it like this in the while() loop runnin my jobThread(boost way):
		boost::mutex::scoped_lock l(m_mutex);		if(m_bPause)		{			m_cond.wait(l);		}


m_bPause becomes true if there are no more jobs left in the queue, and get's false if new jobs get added to it.

This topic is closed to new replies.

Advertisement