June 27th, 2018 (back)

RAPTR - Filesystem, TOML, and Logging

Logging and TOML

Whoo! I stacked a few extra dependencies on the project today, but for good purpose. Today I added:

  • Argument management
  • Filesystem management
  • Logging controls
  • Character loading via TOML
  • Static Mesh loading via TOML
I also solved a number of smaller issues with the build configuration.

Filesystem

The filesystem component was pretty fun to design architecturally. My plans were to use std::experimental::filesystem and a POD that describes a simple path system. So, when the game starts up it registers the game directory and then you request paths from the filesystem, which give you a FileInfo object. This has utility to open files.

class FileInfo {
 public:
  fs::path file_relative;
  fs::path file_path;
  fs::path file_dir;
  fs::path game_root;

 public:
  std::optional<std::ifstream> open(bool binary = true) const;
};

Extremely simple object. The Filesystem class is invoked with a root and then you request paths from it.

auto mesh = StaticMesh::from_toml(filesystem->path("staticmeshes/fire.toml"));

Speaking of TOML. The TinyToml library rocks. It is super easy to use and made configuration easy as heck. And the same goes to spdlog. Also just a great library for logging.

I found this implementation of converting a TOML configuration for a Character object (like our raptr) really easy to write. Those folks really made a clean library.

std::shared_ptr<Character> Character::from_toml(const FileInfo& toml_path)
{
  auto toml_relative = toml_path.file_relative;
  auto ifs = toml_path.open();
  if (!ifs) {
    return nullptr;
  }

  toml::ParseResult pr = toml::parse(*ifs);

  if (!pr.valid()) {
    logger->error("Failed to parse {} with reason {}", toml_relative, pr.errorReason);
    return nullptr;
  }

  const toml::Value& v = pr.value;

  std::string toml_keys[] = {
    "character.name",
    "character.walk_speed",
    "character.run_speed",
    "character.jump_vel",
    "sprite.path",
    "sprite.scale"
  };

  std::map<std::string, const toml::Value*> dict;

  for (const auto& key : toml_keys) {
    const toml::Value* value = v.find(key);
    if (!value) {
      logger->error("{} is missing {}", toml_relative, key);
      return nullptr;
    }
    dict[key] = value;
  }

  std::string sprite_path = dict["sprite.path"]->as<std::string>();
  if (!fs::exists(toml_path.game_root / sprite_path)) {
    logger->error("{} is not a valid sprite path in {}", sprite_path, toml_relative);
  }

  std::shared_ptr<Character> character(new Character());
  FileInfo sprite_file;
  sprite_file.game_root = toml_path.game_root;
  sprite_file.file_path = toml_path.game_root / sprite_path;
  sprite_file.file_relative = sprite_path;
  sprite_file.file_dir = sprite_file.file_path.parent_path();

  character->sprite = Sprite::from_json(sprite_file);
  character->sprite->scale = dict["sprite.scale"]->as<double>();
  character->sprite->set_animation("Idle");
  character->walk_speed = dict["character.walk_speed"]->as<int32_t>();
  character->run_speed = dict["character.run_speed"]->as<int32_t>();
  character->sprite->x = 0;
  character->sprite->y = 0;
  character->position().x = 0;
  character->position().y = 0;
  character->jump_vel = dict["character.jump_vel"]->as<int32_t>();

  return character;
}