summaryrefslogtreecommitdiffstats
path: root/src/core/resource
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/resource')
-rw-r--r--src/core/resource/CMakeLists.txt5
-rw-r--r--src/core/resource/image.cc81
-rw-r--r--src/core/resource/image.hh11
-rw-r--r--src/core/resource/resource.cc126
-rw-r--r--src/core/resource/resource.hh53
5 files changed, 276 insertions, 0 deletions
diff --git a/src/core/resource/CMakeLists.txt b/src/core/resource/CMakeLists.txt
new file mode 100644
index 0000000..840b3c2
--- /dev/null
+++ b/src/core/resource/CMakeLists.txt
@@ -0,0 +1,5 @@
+target_sources(core PRIVATE
+ "${CMAKE_CURRENT_LIST_DIR}/image.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/image.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/resource.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/resource.hh")
diff --git a/src/core/resource/image.cc b/src/core/resource/image.cc
new file mode 100644
index 0000000..8ab98db
--- /dev/null
+++ b/src/core/resource/image.cc
@@ -0,0 +1,81 @@
+#include "core/pch.hh"
+
+#include "core/resource/image.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/io/physfs.hh"
+
+static int stbi_physfs_read(void* context, char* data, int size)
+{
+ return static_cast<int>(PHYSFS_readBytes(reinterpret_cast<PHYSFS_File*>(context), data, size));
+}
+
+static void stbi_physfs_skip(void* context, int count)
+{
+ auto file = reinterpret_cast<PHYSFS_File*>(context);
+ PHYSFS_seek(file, PHYSFS_tell(file) + count);
+}
+
+static int stbi_physfs_eof(void* context)
+{
+ return PHYSFS_eof(reinterpret_cast<PHYSFS_File*>(context));
+}
+
+static const void* image_load_func(const char* name, std::uint32_t flags)
+{
+ assert(name);
+
+ stbi_io_callbacks callbacks;
+ callbacks.read = &stbi_physfs_read;
+ callbacks.skip = &stbi_physfs_skip;
+ callbacks.eof = &stbi_physfs_eof;
+
+ stbi_set_flip_vertically_on_load(bool(flags & IMAGE_LOAD_FLIP));
+
+ auto file = PHYSFS_openRead(name);
+
+ if(file == nullptr) {
+ spdlog::error("image: {}: {}", name, io::physfs_error());
+ return nullptr;
+ }
+
+ int desired_channels;
+
+ if(flags & IMAGE_LOAD_GRAY) {
+ desired_channels = STBI_grey;
+ }
+ else {
+ desired_channels = STBI_rgb_alpha;
+ }
+
+ int width, height, channels;
+ auto pixels = stbi_load_from_callbacks(&callbacks, file, &width, &height, &channels, desired_channels);
+
+ PHYSFS_close(file);
+
+ if(pixels == nullptr) {
+ spdlog::error("image: {}: {}", name, stbi_failure_reason());
+ return nullptr;
+ }
+
+ auto image = new Image;
+ image->pixels = pixels;
+ image->size = glm::ivec2(width, height);
+ return image;
+}
+
+static void image_free_func(const void* resource)
+{
+ assert(resource);
+
+ auto image = reinterpret_cast<const Image*>(resource);
+ stbi_image_free(image->pixels);
+
+ delete image;
+}
+
+void Image::register_resource(void)
+{
+ resource::register_loader<Image>(&image_load_func, &image_free_func);
+}
diff --git a/src/core/resource/image.hh b/src/core/resource/image.hh
new file mode 100644
index 0000000..575591f
--- /dev/null
+++ b/src/core/resource/image.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+constexpr static unsigned int IMAGE_LOAD_GRAY = 0x0001U;
+constexpr static unsigned int IMAGE_LOAD_FLIP = 0x0002U;
+
+struct Image final {
+ static void register_resource(void);
+
+ stbi_uc* pixels;
+ glm::ivec2 size;
+};
diff --git a/src/core/resource/resource.cc b/src/core/resource/resource.cc
new file mode 100644
index 0000000..926dfc5
--- /dev/null
+++ b/src/core/resource/resource.cc
@@ -0,0 +1,126 @@
+#include "core/pch.hh"
+
+#include "core/resource/resource.hh"
+
+struct ResourceLoader final {
+ ResourceLoadFunc load_func;
+ ResourceFreeFunc free_func;
+ emhash8::HashMap<std::string, std::shared_ptr<const void>> resources;
+ std::vector<std::shared_ptr<const void>> cache;
+ std::string class_name;
+};
+
+static emhash8::HashMap<std::type_index, std::unique_ptr<ResourceLoader>> loaders;
+
+void resource::detail::register_loader(const std::type_info& type, ResourceLoadFunc load_func, ResourceFreeFunc free_func)
+{
+ assert(load_func);
+ assert(free_func);
+
+ auto type_index = std::type_index(type);
+ auto loader = std::make_unique<ResourceLoader>();
+ loader->class_name = type.name();
+ loader->load_func = load_func;
+ loader->free_func = free_func;
+
+ assert(!loaders.contains(type_index));
+
+ loaders.insert_or_assign(type_index, std::move(loader));
+}
+
+std::shared_ptr<const void> resource::detail::load_resource(const std::type_info& type, std::string_view name, std::uint32_t flags)
+{
+ auto name_str = std::string(name);
+ auto type_index = std::type_index(type);
+ auto loader = loaders.find(type_index);
+
+ if(loader == loaders.cend()) {
+ spdlog::error("resource: no loader registered for type [{}]", type.name());
+ return nullptr;
+ }
+
+ auto resource_it = loader->second->resources.find(name_str);
+
+ if(resource_it == loader->second->resources.cend()) {
+ auto resource_raw = loader->second->load_func(name_str.c_str(), flags);
+
+ if(resource_raw == nullptr) {
+ spdlog::error("resource: {} [{}]: load failed", loader->second->class_name, name);
+ return nullptr;
+ }
+
+ std::shared_ptr<const void> resource_ptr(resource_raw, [](const void* ptr) { /* empty */ });
+ auto loaded_it = loader->second->resources.insert_or_assign(name_str, std::move(resource_ptr));
+
+ if(flags & RESOURCE_CACHE) {
+ loader->second->cache.push_back(loaded_it.first->second);
+ }
+
+ return loaded_it.first->second;
+ }
+
+ return resource_it->second;
+}
+
+std::shared_ptr<const void> resource::detail::find_resource(const std::type_info& type, std::string_view name)
+{
+ auto name_str = std::string(name);
+ auto type_index = std::type_index(type);
+ auto loader = loaders.find(type_index);
+
+ if(loader == loaders.cend()) {
+ spdlog::error("resource: no loader registered for type [{}]", type.name());
+ return nullptr;
+ }
+
+ auto resource_it = loader->second->resources.find(name_str);
+
+ if(resource_it == loader->second->resources.cend()) {
+ spdlog::error("resource: {} [{}]: not found", loader->second->class_name, name);
+ return nullptr;
+ }
+
+ return resource_it->second;
+}
+
+void resource::hard_cleanup(void)
+{
+ for(auto& [type_index, loader] : loaders) {
+ loader->cache.clear();
+
+ for(auto& [name, resource_ptr] : loader->resources) {
+ if(resource_ptr.use_count() > 1) {
+ spdlog::warn("resource: zombie resource: {} [{}] [use_count={}]", name, loader->class_name, resource_ptr.use_count());
+ }
+ else {
+ spdlog::debug("resource: releasing {} [{}]", name, loader->class_name);
+ }
+
+ loader->free_func(resource_ptr.get());
+ }
+
+ loader->resources.clear();
+ }
+
+ loaders.clear();
+}
+
+void resource::soft_cleanup(void)
+{
+ for(auto& [type_index, loader] : loaders) {
+ auto resource_it = loader->resources.begin();
+
+ while(resource_it != loader->resources.end()) {
+ if(resource_it->second.use_count() <= 1) {
+ spdlog::debug("resource: releasing {} [{}]", resource_it->first, loader->class_name);
+
+ loader->free_func(resource_it->second.get());
+ resource_it = loader->resources.erase(resource_it);
+
+ continue;
+ }
+
+ resource_it = std::next(resource_it);
+ }
+ }
+}
diff --git a/src/core/resource/resource.hh b/src/core/resource/resource.hh
new file mode 100644
index 0000000..105c7ff
--- /dev/null
+++ b/src/core/resource/resource.hh
@@ -0,0 +1,53 @@
+#pragma once
+
+template<typename T>
+using resource_ptr = std::shared_ptr<const T>;
+
+constexpr std::uint32_t RESOURCE_CACHE = 0x00000001U; ///< Cache the resource after loading
+constexpr std::uint32_t RESOURCE_USER = 0xFFFFFF00U; ///< User-defined flags for custom behavior
+
+using ResourceLoadFunc = const void* (*)(const char* name, std::uint32_t flags);
+using ResourceFreeFunc = void (*)(const void* resource);
+
+namespace resource::detail
+{
+void register_loader(const std::type_info& type, ResourceLoadFunc load_func, ResourceFreeFunc free_func);
+resource_ptr<void> load_resource(const std::type_info& type, std::string_view name, std::uint32_t flags);
+resource_ptr<void> find_resource(const std::type_info& type, std::string_view name);
+} // namespace resource::detail
+
+namespace resource
+{
+template<typename T>
+void register_loader(ResourceLoadFunc load_func, ResourceFreeFunc free_func);
+template<typename T>
+resource_ptr<T> load(std::string_view name, std::uint32_t flags = 0U);
+template<typename T>
+resource_ptr<T> find(std::string_view name);
+} // namespace resource
+
+namespace resource
+{
+void hard_cleanup(void);
+void soft_cleanup(void);
+} // namespace resource
+
+template<typename T>
+void resource::register_loader(ResourceLoadFunc load_func, ResourceFreeFunc free_func)
+{
+ resource::detail::register_loader(typeid(T), load_func, free_func);
+}
+
+template<typename T>
+resource_ptr<T> resource::load(std::string_view name, std::uint32_t flags)
+{
+ auto result = resource::detail::load_resource(typeid(T), name, flags);
+ return std::reinterpret_pointer_cast<const T>(result);
+}
+
+template<typename T>
+resource_ptr<T> resource::find(std::string_view name)
+{
+ auto result = resource::detail::find_resource(typeid(T), name);
+ return std::reinterpret_pointer_cast<const T>(result);
+}