CytopiaTeam / Cytopia

:deciduous_tree::house_with_garden::office::evergreen_tree: A city building simulation game
https://www.cytopia.net
GNU General Public License v3.0
1.98k stars 105 forks source link

Create ResourceManager service #311

Closed ghost closed 4 years ago

ghost commented 5 years ago

We would like to have more control over Cytopia's time and memory usage of handling resources. A cache would allow us to do this. This task involves creating a new service object named ResourceManager. This service will receive queries from other services to provide game resources (sound WAV data, Textures, etc). Its goal is to minimize the amount of time and memory needed to run Cytopia at any given amount of time. This service should be made available only to GameServices that require it. We assume that its API will only be used from the GameLoop Thread.

Algorithm

To achieve this, we propose this simple algorithm:

Every time a resource is needed, we must fetch it and load it in memory. Each resource must be paired with an age which is a uint32. There must also be an initial global "current" age which starts at 0. At every query, the resource that is requested must be loaded if not already and its age must be set to the current age. Then, we must increment the current age. We must also keep a measure of the cache's memory usage. This can be done with a uint32 field that counts the number of bytes used by the cache. There should be a compile-time constant that specifies the maximum memory size in bytes. When the cache must load a new resource and the size of memory usage exceeds the configured limit or the age becomes MAX_UINT32, then we must reindex young resources and evict old resources. This can be done quickly by partially sorting the keys by their age (std::partial_sort) up to 10 resources (debatable and configurable?). Then, we must reindex these resources so that their age is near 0 (either by setting them all to 0 or using modulo arithmetic). Finally, the current age must be set to 1. The other resources that didn't make the top 10 must be evicted. The top 10 approach has a weakness: If the top 10 have a larger size than the memory limit, each query will reindex and clean up. A probably better implementation is to find the top 50% or even the top 30% instead, that way we are always reducing the memory usage at each clean-up pass. It is up to the developer to chose how they want to implement this.

Interface and Representation

We would like the cache to hold many kinds of resources, yet still being type-safe. This means that our APIs must be overloaded to accommodate each type. For now, we only require the following resources to be accessible:

The following public API should be made available:

Thread safety

It is the cache's responsibility to ensure that the resources given are thread-safe because there is a possibility of pruning the cache while a resource is still being used. To achieve this, each resource returned by the ::Get API must be a shared_ptr. If the client service that requires a resource needs it for a long amount of time, it is their responsibility to preserve a copy of this shared_ptr so that the underlying resource remains alive. Hence, even if the cache has control over the resources it owns, it's possible for another service to leak the memory.

bangboomboomboom commented 5 years ago

For anyone implementing this. There already exists a resourcemanager class but this is just a json loader, it does not have the functionality mentioned above. This functionality needs to be added to a class that will preferably replace the old resourcemanager.

ghost commented 4 years ago

Fixed by #334