Disch wrote: |
---|
That can't be the only place, can it? If you're handing generic resources to outside code... then the outside code must also be doing some sort of downcasting to actually use the resource, right? |
It really is; here's an example of using it:
1 2 3 4 5 6 7 8
|
void bgobject::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
auto weak_res = resource_manager::get(m_texture_id);
auto res = weak_res.lock();
sf::Sprite sprite(res->texture);
sprite.setPosition(position());
target.draw(sprite, states);
}
|
The draw function knows it wants a texture, so all it does it retrieve the resource struct and then use the texture. It
could use the sf::Music member instead, which would be bad, but while I generally don't use the "honour system" in programming, I made an exception there for simplicity's sake. While code could be written that uses sf::Music when it's supposed to use sf::Texture, the person writing that code would have to be doing it intentionally because they had to request the resource in the first place. The resource IDs even begin with the first letter of the kind of resource they represent, e.g. t for texture, s for shader, m for music or sb for sound buffer (since the type is sf::SoundBuffer). You really would have to do it on purpose, and if someone is that determined to break the code they're writing, they have much easier methods at their disposal, like std::terminate.
If you need to add a new resource type you have to do several steps:
1) Create a new loader
2) Add another enum/identifier to mark the resource type ID
3) Add another entry in your union
4) Update all switch-on-types to include the new type (hopefully you only have one -- but that seems unlikely -- and is impossible to guarantee with outside code) |
1. I don't know where "create a new loader" came from: the loader and manager are not aware that there are different resource types (only the resource class itself is), so I only need one loader with my current architecture.
2. I'd have to create another identifier for the resource type anyway because the resource type is how the engine constructs the path to the resource (it's essentially "assets/" + resource_type + "/" + resource_id + "." + extension).*
3. That's true.
4. Also true, but I really do only have one switch (see above). The default case of the switch throws an exception so that I don't forget to add the new case. See, I thought ahead :) Of course, that exception is only thrown if the new resource type is actually used, but that's the only situation where it would cause a bug anyway.
* that information
does currently have to be updated by hand, which is definitely a problem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
std::map<resource_type, std::string> resource::paths = {
{ resource_type::font, "assets/fonts" },
{ resource_type::frag_shader, "assets/shaders" },
{ resource_type::music, "assets/music" },
{ resource_type::sound, "assets/sounds" },
{ resource_type::texture, "assets/textures" },
{ resource_type::vert_shader, "assets/shaders" }
};
std::map<resource_id, resource_type> resource::types = {
{ resource_id::tbgcity000, resource_type::texture }
};
std::map<resource_id, std::string> resource::filenames = {
{ resource_id::tbgcity000, "bgcity000.png" }
};
|
but that's only temporary, I plan to have it loaded automatically from configuration files just as soon as I can figure out how to convert strings to enum values without having more switch()es like you can in C#.
OOP, silly goose. You don't have to manage multiple different arrays.. you just have to manage 1 inside a class. Then instantiate multiple classes. |
But
1 2 3 4 5 6 7 8 9 10 11 12
|
template <class TResource>
class resource_manager {
private:
friend void Zarathustra();
static std::map<resource_id, TResource> resources;
};
void Zarathustra()
{
std::cout << std::boolalpha << &(resource_manager<sf::Texture>::resources) ==
&(resource_manager<sf::Shader>::resources);
}
|
false |
Thus Spake Zarathustra.
If I were a goose, wouldn't I be migrating south for the winter and unable to use the Internet?
I don't see how. All the loader does is load a resource, right? What does that have to do with how many managers there are? The loader shouldn't even need to be aware of any -- or at worst.. it would only need to be aware of the one it's working with. |
Well, in the main thread, objects call the resource manager's "load" method, which then calls the resource loader's request method which stores the ID of the requested resource. Then, the gameplay state pushes a "loading" state which calls the resource loader's "load" method. The load method tells the background thread to stop idling. The background thread moves the contents of the queue into a temporary (so that the process doesn't block while the resources are loading, only while the queue is being moved which I assume is very fast) and then starts loading the requested resources. When it's done, it informs the main thread (via an atomic variable that is accessed with an accessor function) and then goes back to sleep. When that happens, in the main thread, the loading state returns control to the gameplay state and gameplay continues. Although the non-blocking is kind of pointless at the moment because the game blocks anyway, I plan to have some pre-loading going on in the background in the future.
Anyway, you're right that with my current design, there wouldn't be any need for more loaders or more threads because the resource loader doesn't even know what kind of resources its loading. That is, again, pushed onto the resource thread. In fact, it doesn't even do the loading itself, directly - that happens in the resource loader's constructor. This way, neither the resource manager nor the resource loader are even aware that there are different types of resource. In fact, only the resource struct itself knows. Everything else is based around the belief "I put an x in, so I must get an x out".