Managing Content and the Joys of Smart Pointers

Hello everyone, I figured I’d write a post about something I’ve been working on for the last week or so, partly as a bit of advice for some budding programmer wanting to write a bit more low level games programming stuff, but also to try and explain why it has been several weeks since I last posted about a small prototype I’ve been working on, and why I have been posting less stuff on Twitter than usual.

To put it basically, a lot of C++ work, mostly involving Gemstone Keeper. I decided to go back to it one more time after a lot of bugs and feedback came in. By the time I’ve posted this, Gemstone Keeper has been updated to version 1.0.3, which includes bug fixes, UI updates and two new enemy types.

j7fcaz6

 

While most of this wasn’t planned (most of this would have happened much earlier if I was notified on Steam within days of it being reported), but it had been on mind for a particular reason.

To put it simply, I have been updating my C++ game framework, the Vigilante Game Framework. This is the framework that I had built up alongside the game it was made for, Gemstone Keeper, before open-sourcing it in the hopes that it could be improved for more projects. I noticed a problem with the content management system, and as such I had spent the last two weeks researching and implementing a new way that would make it more optimal while keeping it stable. Because of this, Gemstone Keeper will soon be updated with this improvement among other bug fixes, and any future games using the VFramework will feature this too.

dytc50n


Here is the original code for loading and setting an sf::Texture in my content system, according to the repository for Gemstone Keeper this was initally written around Feburary 2016, and aside from one change in April this remained how it was:

bool VContent::LoadTexture(const sf::String& name, sf::Texture &texture)
{
   if (FindTexture(name))
   {
      texture = textureDir[name];
      return true;
   }
   else
   {
      if (texture.loadFromFile(name))
      {
         VLog("%s loaded", name.toAnsiString().c_str());
         textureDir.insert(textureDir.begin(), std::pair<sf::String, sf::Texture>(name, texture));
         return true;
      }
   }

   VLog("Error loading texture: %s", name.toAnsiString().c_str());
   return false;
}

The approach should be fairly straightforward:

  • If a texture exists, set it to the texture object and return true.
  • If it doesn’t exist, load it using the texture object. Return true if load is successful.
  • If it cannot load, return false.

However there is one problem, setting the texture object doesn’t set the reference, despite passing the object in as a reference (hence the &). Instead it creates a copy, with a new texture ID and references.

Performance wise, this doesn’t have much of an issue, however memory wise there could be a concern with creating several sprites with the intention of using the same texture, but it isn’t. The intent of a content manager is to load, unload and store a single instance of an asset, with any objects merely referencing that content. I investigated a number of approaches (and spoke with eXpl0it3r of the SFML team) to find the right one.

The approach I finally went with required the following changes:

  • The load function should return the object itself as a non-pointer reference, not a bool. This is to ensure that if the loading or finding process goes wrong, the program should let you know and fail. The reason for a non-pointer is to ensure you wouldn’t return a NULL object. Returning a reference makes sure not to return a newly created copy.
  • If it cannot be loaded, an exception should be thrown. Exception handling is an ideal alternative to responding to function returning false, because it’ll make a function closing on failure much easier.
  • The texture should be stored as a unique pointer.

The reason for a unique pointer, instead of a regular or “raw” pointer, is to ensure that the object only has one true owner, and that once it’s dereferenced (either by closing the game or unloading it) it’ll be properly destroyed and unable to be used by other objects, all with little to no difference in performance. While a unique pointer cannot have more than one reference, the raw pointer can still be obtained and used, just make sure that once the content is unloaded there aren’t any active objects still referencing it.

This is what the texture loading function looks like now (from the Github Repository):

sf::Texture& VContent::LoadTexture(const sf::String& name)
{
   auto found = textureDir.find(name);
   if (found == textureDir.end())
   {
      std::unique_ptr<sf::Texture> texture = std::unique_ptr<sf::Texture>(new sf::Texture());
      if (texture->loadFromFile(name))
      {
         VBase::VLog("%s loaded", name.toAnsiString().c_str());
         textureDir[name] = std::move(texture);
      }
      else
      {
 #ifndef __linux__
         throw std::exception(("Error loading texture: " + name.toAnsiString()).c_str());
 #else
         throw ("Error loading texture: " + name.toAnsiString());
 #endif
      }
   }

   return *textureDir[name];
}

To use the functions in this format, I had to change a lot of objects around the game. Textures, Images, Fonts and SoundBuffers all changed to use raw pointers, and the returned values from the functions are converted to functions as well.

texture = &VGlobal::p()->Content->LoadTexture(filename);
sprite.setTexture(*texture);

This ensured that the object being set is has the same reference as the object being returned, no more copying data, no more instantiating new objects to handle the copy.

I’ve also turned a lot of other objects into unique pointers, particularly the objects in VGlobal, which is intended to only have one instance of. It’s ideal since these ones are all destroyed at the end, and in a scenario where an object could be replaced with another one, the unique pointer can destroy the old one in order to replace the new one.

Other small changes I’ve made to update the framework includes all objects that load textures now having an optional area rectangle. This was mainly put in if multiple sprites use the same texture, but different atlases. Best example would be in the LoadGraphic and setSize functions of VSprite, where the default parameter will ensure the whole texture is used, else the animation system will restrict the render area of the sprite to that rectangle. While sf::Texture does have the ability to load a rectangle area of a texture to an object, I feel like this only adds a separate texture, instead of the one loaded and taken from VContent. At some point I’d like to use this more for Gemstone Keeper and other games, as I see reducing the texture count and having more render calls using the same texture would have additional benefits, no matter how small.

Hopefully more changes like these will benefit games I make with this framework, I’d also like to use more smart pointers, maybe shared pointers for my general objects, but for the time being, I’ve got a project to get back to.

Advertisements