I'm a hobbyist C++
and DirectX
programmer, so most of the knowledge I have is from old game development books in which the code designs are just to get something up and running as a demonstration, leaving me with a lot of design considerations for even the simplest of programs. During development of such a program, I recently learned of RAII
, so I decided to give this design pattern a shot because from what I understood, an object should be usable and valid upon construction and this greatly simplifies the way objects can be used by the program. Previously, I had been using a create()
& destroy()
pattern in some of my objects which lead to a lot of validation checking in most member functions.
In the program's architecture, I have very few graphics objects that are wrappers around DirectX
resources, one of them being a Texture
object. If I want to render tile map for example, I could have Tile
objects that are constructed with pointers to AnimatedImage
objects which are constructed with pointers to Texture
objects.
The problem with using DirectX
is that there are times in which the graphics device becomes lost, such as a driver update for the video card during program execution. When these events happen, the existing graphics resources must be released and reacquired to continue rendering normally, including the destruction and reconstruction of the Texture
objects. This makes the use of an RAII
design pattern seem like it may not be the best choice. I would need recreate the Texture
objects, recreate the AnimatedImage
objects, and then recreate the Tile
objects. It seems like an extreme hassle because some objects that are recreated will contain more than just image data.
So if we start out with some example code (not exact, but it serves its purpose):
// Construct graphics objects into some structure we will pass around.
graphics.pDevice = new GraphicsDevice(windowHandle, screenWitdth, screenHeight);
graphics.pTexture1 = new Texture(graphics.pDevice, width1, height1, pPixelData1);
graphics.pTexture2 = new Texture(graphics.pDevice, width2, height2, pPixelData2);
graphics.pSpriteBuffer = new SpriteBuffer(graphics.pDevice, maxSprites);
Elsewhere in the program that builds objects for a tile map:
// Construct some in-game animations.
images.pGrass = new AnimatedImage(graphics.pTexture1, coordinates1[4], duration1);
images.pWater = new AnimatedImage(graphics.pTexture2, coordinates2[4], duration2);
// Construct objects to display the animation and contain physical attributes.
thisMap.pMeadowTile = new Tile(images.pGrass, TILE_ATTRIBUTE_SOFT);
thisMap.pPondTile = new Tile(images.pWater, TILE_ATTRIBUTE_SWIMMABLE);
Then during the rendering routine:
while (gameState.isRunning())
{
graphics.pSpriteBuffer->clear();
thisMap.bufferSprites(graphics.pSpriteBuffer);
graphics.pSpriteBuffer->draw();
if (graphics.pDevice->present() == RESULT_COULD_NOT_COMPLETE)
{
// Uh oh! The device has been lost!
// We need to release and recreate all graphics objects or we cannot render.
// Let's destruct the sprite buffer, textures, and graphics device.
// But wait, our animations were constructed with textures, the pointers are
// no longer valid and must be destructed.
// Come to think of it, the tiles were created with animations, so they too
// must be destructed, which is a hassle since their physical attributes
// really are unrelated to the graphics.
// Oh no, what other objects have graphical dependencies must we consider?
}
}
Is there some design concept I am missing here or is this one of those cases in which RAII
works, but at a unnecessarily large cost if there is a lot of object to object dependency? Are there any known design patterns that are specifically suited for this scenario?
Here were some of the ways I have thought of approaching this:
Equip the graphics objects with a
recreate()
method. The advantage is that any object that points to a Texture can retain that pointer without being destroyed. The disadvantage is that if the reacquisition fails, I would be left with a zombie object which is no better than thecreate()
&destroy()
pattern.Add a level of indirection using a registry of all graphics objects that will return an index to a
Texture
pointer or a pointer to aTexture
pointer so that existing objects that rely on graphics don't need to be destroyed. Advantages and disadvantages are the same as above with the added disadvantage of additional overhead from the indirection.Store the current state of the program and unwind back until the graphics objects have been reacquired, then rebuild the program in the state it was in. No real advantage I can think of, but seems the most
RAII
appropriate. The disadvantage is the complexity in implementing this for a scenario that is not too common.Completely segregate all visual representations of objects from their physical representations. The advantage is that only the necessary objects to be recreated actually are, which can leave the rest of the program in a valid state. The disadvantage is that the physical and visual objects still need to know about each other in some way, which may lead to some bloated object management code.
Abort program execution. The advantage is that this is as easy as it gets and very little work is spent for something that does not often happen. The disadvantage is that it would be frustrating to anyone using the program.
First solution
That's a good question for the late 2000ths (at least for desktop graphics). In 2015 you'd better forget DirectX 9 with it's device lost and do DirectX 11 (or even upcoming DirectX 12).
Second solution
If you still want to stick with deprecated API (or if you are using same time something like OpenGL ES on mobile devices, where context loss is a common event), there is a way that works pretty well (among others). Basically it is a mix of yours
and
Here it is:
refactor your code with a Factory pattern: force user to allocate new resources with functions (wrap
new
,std::make_shared
or whatever you use inside them)auto resource = device->createResource(param0, param1);
make factory remember resources somehow
make resources remember it's parameters (they could be changed if necessary during runtime, but should be saved as well. For big or costly parameter objects use Proxy pattern)
on device lost event, release all objects, than recreate them
You can build more complicated and intelligent system on top of that.
Related notes:
It is not specific to device lost event. Handle resource fails immediately, before giving up control to the user, same way that you do when you create resource first time and it fails (by throwing an exception (so user could handle it), or by using placeholder resource or by shutting down the application, or anything else -- you to decide)
Must have. Not even a question to discuss, unless you are building a tetris. Use MVC or modern stuff like ECS. Never store your
Mesh
insidePlayer
, and yourParticleEmitter
insideFireball
. Never even make them know each other.It's very useful. What you've described is a "save game" / "load game" mechanics. It could also be used to implement "replay" functionality and game-engine movies. Note (as to add to point 2), your save data will never include visual representations (Unless you want multi-gigabyte save files).
Don't overengineer. Most games don't bother at all. They handle device lost in the same way as if user did "Save game" -> "Exit" -> "Load game", designing startup facilities appropriately.
Another way to use by itself or in combination with factory is Lazy initialization: make your resource to verify both if it's valid itself and device lost.
It adds some overhead each time resource is accessed, but it's a safe and very simple to implement way to ensure your resource is up when it's needed