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.