Skip to content

Commit

Permalink
Change quite a few things to the cache and loader API
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonMaracine committed Jul 16, 2024
1 parent da6c87d commit 48a66ec
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 42 deletions.
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,70 @@ set(RESMANAGER_BUILD_EXAMPLE ON)
Check out `example/main.cpp` for some examples. Read the source files for some documentation.

Development takes place on the `main` branch. `stable` is for actual use.

## Example

```c++
#include <memory>
#include <string>
#include <resmanager/resmanager.hpp>

// Some resource type
class Image {
public:
explicit Image(const std::string& file_path) {
// ...
}
private:
// ...
};

void some_image_initialization_function(std::shared_ptr<Image> image) {
// ...
}

class Loader {
public:
// Example 1
std::shared_ptr<Image> load_image1(resmanager::HashedStr64 id, const std::string& file_path) {
// Load the resource if it's not in the cache
// If it's in the cache, return it
// Thus, this function can be called multiple times
return images.load(id, file_path);
}

// Example 2
std::shared_ptr<Image> load_image2(resmanager::HashedStr64 id, const std::string& file_path) {
// Same as load(), but also return if it was accessed from the cache or loaded
const auto [image, present] {images.load_check(id, file_path)};

if (!present) {
// This needs to be called only once
some_image_initialization_function(image);
}

return image;
}

// Example 3
std::shared_ptr<Image> load_image3(resmanager::HashedStr64 id, const std::string& file_path) {
// Check if already present in the cache
// If so, access it directly
if (images.contans(id)) {
return images.get(id);
}

// Because we already checked if it's in the cache, we load it directly
const auto image {images.force_load(id, file_path)};

// This is still called only once
some_image_initialization_function(image);

return image;
}
private:
resmanager::Cache<Image> images;

// Caches for other resource types...
};
```
20 changes: 10 additions & 10 deletions example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <cstdint>
#include <utility>

#include "resmanager/resmanager.hpp"
#include <resmanager/resmanager.hpp>

// If you have some smart pointer type...
template<typename T>
Expand All @@ -22,15 +22,15 @@ Bar<T> some_function_that_constructs_a_resource(Args&&...) {
// You can specialize the loader with your own pointer type
template<typename T>
struct resmanager::Loader<T, Bar<T>> {
using ResourceType = Bar<T>;
using ResourcePointerType = Bar<T>;

template<typename... Args>
ResourceType operator()(Args&&... args) const {
ResourcePointerType operator()(Args&&... args) const {
return some_function_that_constructs_a_resource<T>(std::forward<Args>(args)...);
}
};

// Some example resource
// Some resource
struct Image {
unsigned int w {0};
unsigned int h {0};
Expand All @@ -44,7 +44,7 @@ int main() {

{
resmanager::Cache<int, resmanager::Loader<int, Bar<int>>> cache;
auto foo {cache.load("foo"_H)};
const auto foo {cache.load("foo"_H)};
std::cout << foo.data << '\n';
}

Expand All @@ -70,12 +70,12 @@ int main() {
resmanager::Cache<Image> cache;

// `foo` created here
auto foo {cache.load("foo"_H)};
const auto foo {cache.load("foo"_H)};
foo->w = 400;
std::cout << foo->w << '\n';

// `foo` only referenced here
auto foo2 {cache.load("foo"_H)};
const auto foo2 {cache.load("foo"_H)};
std::cout << foo2->w << '\n';
}

Expand All @@ -84,15 +84,15 @@ int main() {
resmanager::Cache<Image, resmanager::DefaultLoader<Image>, resmanager::HashedStr32> cache;

// `foo` created here
auto foo {cache.load("foo"_h)};
const auto foo {cache.load("foo"_h)};

// `bar` created here
auto bar {cache.load("bar"_h)};
const auto bar {cache.load("bar"_h)};
bar->w = 800;
std::cout << bar->w << '\n';

// `bar` only referenced here
auto bar2 {cache.load("bar"_h)};
const auto bar2 {cache.load("bar"_h)};
std::cout << bar2->w << '\n';

// `foo` deleted here; local reference is still valid
Expand Down
45 changes: 21 additions & 24 deletions src/resmanager/detail/cache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <unordered_map>
#include <cstddef>
#include <utility>
#include <type_traits>

#include "loader.hpp"
#include "hashing.hpp"
Expand All @@ -11,59 +12,55 @@ namespace resmanager {
template<typename T, typename L = DefaultLoader<T>, typename K = HashedStr64, typename H = Hash<K>>
class Cache {
public:
using ResourceType = typename L::ResourceType;
using ResourcePointerType = typename L::ResourcePointerType;

static_assert(std::is_default_constructible_v<ResourcePointerType>);
static_assert(std::is_copy_constructible_v<ResourcePointerType>);
static_assert(std::is_copy_assignable_v<ResourcePointerType>);

// If already present, return the resource directly, otherwise load and then return it
template<typename... Args>
ResourceType load(const K id, Args&&... args) {
ResourcePointerType load(const K id, Args&&... args) {
return load_check(id, std::forward<Args>(args)...).first;
}

// If already present, return the resource directly, otherwise load and then return it
// Also return true if the resource was already in the cache, false otherwise
template<typename... Args>
std::pair<ResourceType, bool> load_check(const K id, Args&&... args) {
std::pair<ResourcePointerType, bool> load_check(const K id, Args&&... args) {
if (auto iter = cache.find(id); iter != cache.end()) {
return std::make_pair(iter->second, true);
} else {
const L loader {};
ResourceType resource {loader(std::forward<Args>(args)...)};
cache[id] = resource;

return std::make_pair(resource, false);
return std::make_pair(force_load(id, std::forward<Args>(args)...), false);
}
}

// Load the resource and replace the old one, if already present
// Just load the resource, replacing the old one, if already present
template<typename... Args>
ResourceType force_load(const K id, Args&&... args) {
ResourcePointerType force_load(const K id, Args&&... args) {
const L loader {};
ResourceType resource {loader(std::forward<Args>(args)...)};
ResourcePointerType resource {loader(std::forward<Args>(args)...)};
cache[id] = resource;

return resource;
}

// Get the resource; throws std::out_of_range, if resource is not found
ResourceType operator[](const K id) const {
return cache.at(id); // TODO maybe handle exception here and return null
// Get the resource; throws std::out_of_range, if the resource is not in the cache
ResourcePointerType get(const K id) const {
return cache.at(id);
}

// Check if the resource is present
bool contains(const K id) const {
return cache.find(id) != cache.cend();
}

// Release and return the resource
ResourceType release(const K id) {
if (auto iter = cache.find(id); iter != cache.end()) {
ResourceType resource {std::move(iter->second)};
cache.erase(id);

return resource;
}
// Release and return the resource; throws std::out_of_range, if the resource is not in the cache
ResourcePointerType release(const K id) {
ResourcePointerType resource {cache.at(id)};
cache.erase(id);

return nullptr;
return resource;
}

// Merge the other cache into this cache
Expand Down Expand Up @@ -94,6 +91,6 @@ namespace resmanager {
return cache.empty();
}
private:
std::unordered_map<K, ResourceType, H> cache;
std::unordered_map<K, ResourcePointerType, H> cache;
};
}
14 changes: 7 additions & 7 deletions src/resmanager/detail/loader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@

namespace resmanager {
// Generic loader
// The resource type must be some sort of smart pointer
// and it must be constructible from nullptr
// The resource pointer type must be some sort of smart pointer, like std::shared_ptr
// It must have copy semantics and be default-constructible
template<typename T, typename R>
struct Loader {
using ResourceType = R;
using ResourcePointerType = R;

template<typename... Args>
ResourceType operator()(Args&&... args) const {
return ResourceType(std::forward<Args>(args)...);
ResourcePointerType operator()(Args&&... args) const {
return ResourcePointerType(std::forward<Args>(args)...);
}
};

// Specialized loader for std::shared_ptr
template<typename T>
struct Loader<T, std::shared_ptr<T>> {
using ResourceType = std::shared_ptr<T>;
using ResourcePointerType = std::shared_ptr<T>;

template<typename... Args>
ResourceType operator()(Args&&... args) const {
ResourcePointerType operator()(Args&&... args) const {
return std::make_shared<T>(std::forward<Args>(args)...);
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/resmanager/detail/version.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

namespace resmanager {
inline constexpr unsigned int VERSION_MAJOR {0};
inline constexpr unsigned int VERSION_MINOR {9};
inline constexpr unsigned int VERSION_MINOR {10};
inline constexpr unsigned int VERSION_PATCH {0};
}

0 comments on commit 48a66ec

Please sign in to comment.