So here is the issue: In my simple engine I’m developing that are basically two (or more) working directories. There is the game, the engine and the tools, but let’s focus on the game and on the engine.
Obviously, the game will have its own assets, so we need a working path that the game project can easily access to load said assets. But it is also important that the engine have its own assets, which I call “Default Assets”, very creative, eh.
And sure, it’s not difficult to set up the engine default assets path in relation to the game project path. But what about when I’m working directly in the engine?
Let me illustrate that with a better example.
When I’m working directly in the engine, the DefaultAssets folder path is "./src/DefaultAssets"
- not the greatest path, I know, why the assets are on the source folder?! But let’s not get caught in details.
When I’m working on the sandbox project, usually for testing things in the engine, the DefaultAssets folder path is "../visions2D/src/DefaultAssets"
And now, let’s take a look at the last sample game I’ve made: Flappy ASCII. The path for the DefaultAssets folder in this case would be: "../visions2D/visions2D/src/DefaultAssets"
- an even worst path, I know, why there are two visions2D folder?
My engine has problems, ok, I know.
But the question I want to pose is: Wouldn’t it be nice if the engine could figure out by itself a “root engine folder” and a “root current project folder”? - And then we effectively have two working paths and we can design an API to search on each one, and a general API that will search on both.
To try to answer that I did some research on the std::filesystem library implemented in C++17.
You can check compiler compatibility here: std::filesystem compiler support.
There is a lot going on here, and the official documentation will be your best friend. I didn’t really find a lot of usage, concrete examples or whatnot with this filesystem, as it is fairly recent. What I did find, though, is this implementation of something similar but compatible way back to C++11.
Classes-wise, your best friends are going to be: path, directory_entry, and directory_iterator.
As for non-member functions and file types, current_path, exists, and is_directory are also pretty nice.
And to be fair, what the documentation and these classes entail make it very straightforward to solve the problem I want to solve. It’s also worth noting that the path class has a lot of helpful functions!
On the example I mentioned above: How do we find the default assets folder on the engine given that we are on the sandbox?!
We can start with this nice little thing:
std::filesystem::path sandboxpath(std::filesystem::current_path());
Since we know that the visions2D folder and the sandbox folder are on the same level, we can use the nice replace_filename()
function provided to us:
std::filesystem::path visions2Droot = defaultassets.replace_filename(std::filesystem::path("visions2D"));
Another useful operation we can perform here is: sandboxpath.parent_path()
With that, I would have the root of all the roots, on that folder I know I’m one above sandbox, and one above the visions2D core engine, and I can use the root path to pretty much search for anything I need.
But anyway, we have the visions2D core engine path now, how do we search on the files and folders inside of it?
for (const std::filesystem::directory_entry& v22dir : std::filesystem::directory_iterator(visions2Droot))
oof, there’s a lot to unpack here. We are iterating with:
const std::filesystem::directory_entry&
That’s why I mentioned directory_entry
would be a nice thing to read at, but you can just auto it.
for (const auto& v22dir : std::filesystem::directory_iterator(visions2Droot))
directory_iterator
provides us with a nice constructor that accepts a path. And now that we are iterating on the folder, we check if the individual entries are folders, and if they are we iterate on them, until we find a folder called “DefaultAssets”.
Here’s an example:
for (const auto& visions2Dentry : std::filesystem::directory_iterator(visions2Droot)) {
if (std::filesystem::is_directory(visions2Dentry)) {
for (auto const& subdir : std::filesystem::directory_iterator(visions2Dentry)) {
if (std::filesystem::is_directory(subdir)) {
if (subdir.path().filename() == "DefaultAssets") {
LOG_INFO("Bingo!");
}
}
}
}
}
Of course there is a lot of repetition here that could be better organized with functions (hey, maybe even recursive functions!) - But it’s just an example.
At the end of this code, the path we desire will be at subdir.path()
We did it! Next step is designing an API and accounting for every edge-case out there!