| |
 | Posted by Rene Zwanenburg in reply to Begah | Permalink Reply |
|
Rene Zwanenburg 
| On Wednesday, 1 June 2016 at 18:14:33 UTC, Begah wrote:
> I started using reference counters for my assets in my application :
> - Images
> - Models
> - ....
For my resource manager I started out with something similar to what you're describing, but I eventually changed the design which turned out to be an immense improvement so I'll just describe that ^^
- Differentiate between the type implementing a resource, and handles to a resource.
- Split up the resource manager, which stores the 'real' resource for a given handle, and the cache where you look up resources by name.
A stripped down version of the resource manager looks something like this:
============
template ResourceManager(ResourceType)
{
struct HandleImpl
{
~this()
{
// Destroy the resource, make sure you mark the corresponding slot in the array as free
}
size_t slot;
}
alias Handle = RefCounted!HandleImpl;
private ResourceType[] resources;
// When creating a resource add it to the manager and pass around the opaque handle instead.
Handle add(ResourceType resource)
{
// Find an empty slot in the resources array, put the resource there, and return a handle pointing to that slot.
}
// This should be used sparingly. Most code shouldn't care about resource implementation. The parts that do can get at it via this function, but shouldn't hold on to the reference longer than necessary.
ResourceType getActualResource(Handle handle) { ... }
// This allows you to hot-swap resources while keeping the resource implementation logically const. Hot-swapping is extremely valuable, I've always found myself wanting to support it sooner or later. I've tried to do it in different ways, but it always ended up in a mess. Using handles on the other hand turned out to be clean and easy.
void update(Handle handle, ResourceType newResource) { ... }
}
============
Note that it doesn't allow you to search for resource by name or anything, this is just a simple mapping between handles and the resource implementation. It doesn't store instances of the RefCounted handles itself, so there are no issues with resources not being cleaned up.
Now when you have this in place you can build a caching mechanism on top of it:
============
template ResourceCache(ResourceType, alias loadByName)
{
alias Manager = ResourceManager!ResourceType;
Manager.Handle[string] cache;
Manager.Handle get(string name)
{
// Check if the resource is already in the cache, if not load it using the loadByName function and store it.
}
void purge()
{
// the keys property allocates a new array with the AA keys, which is important since we're modifying the AA.
foreach(key; cache.keys)
{
// This is the part you were actually interested in. It's important to take a pointer to the handle here, and not reference it directly. I had some issues with that in the past, I'm not sure if that's still the case.
auto ptr = key in cache;
if(ptr.refCountedStore.refCount == 1) // If this is the last reference
{
destroy(*ptr); // I'm not sure if this is still necessary, there was some issue with AAs in the past. It may be fixed today. Destroying the handle doesn't hurt either way so I left it in.
cache.remove(key);
}
}
}
}
============
So cached resources don't get cleaned automatically if you don't call purge, but that's usually the right thing to do anyways. For example when you're changing scenes (I assume this is for some kind of game?) you can simply destroy the current scene, load another, then purge the cache, without having to reload resources used by both scene 1 and 2.
Another upside is that this doesn't require every resource to be named / backed by file. Resources can be dynamically generated at runtime without special casing anywhere.
|