From f40d09cb8f712e87691af4912f3630d92d692779 Mon Sep 17 00:00:00 2001 From: untodesu Date: Thu, 11 Dec 2025 15:14:26 +0500 Subject: Shuffle stuff around - Use the new and improved hierarchy I figured out when making Prospero chat - Re-add NSIS scripts, again from Prospero - Update most build and utility scripts with their most recent versions --- src/core/resource/CMakeLists.txt | 5 ++ src/core/resource/image.cc | 81 +++++++++++++++++++++++++ src/core/resource/image.hh | 11 ++++ src/core/resource/resource.cc | 126 +++++++++++++++++++++++++++++++++++++++ src/core/resource/resource.hh | 53 ++++++++++++++++ 5 files changed, 276 insertions(+) create mode 100644 src/core/resource/CMakeLists.txt create mode 100644 src/core/resource/image.cc create mode 100644 src/core/resource/image.hh create mode 100644 src/core/resource/resource.cc create mode 100644 src/core/resource/resource.hh (limited to 'src/core/resource') 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(PHYSFS_readBytes(reinterpret_cast(context), data, size)); +} + +static void stbi_physfs_skip(void* context, int count) +{ + auto file = reinterpret_cast(context); + PHYSFS_seek(file, PHYSFS_tell(file) + count); +} + +static int stbi_physfs_eof(void* context) +{ + return PHYSFS_eof(reinterpret_cast(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(resource); + stbi_image_free(image->pixels); + + delete image; +} + +void Image::register_resource(void) +{ + resource::register_loader(&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> resources; + std::vector> cache; + std::string class_name; +}; + +static emhash8::HashMap> 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(); + 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 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 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 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 +using resource_ptr = std::shared_ptr; + +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 load_resource(const std::type_info& type, std::string_view name, std::uint32_t flags); +resource_ptr find_resource(const std::type_info& type, std::string_view name); +} // namespace resource::detail + +namespace resource +{ +template +void register_loader(ResourceLoadFunc load_func, ResourceFreeFunc free_func); +template +resource_ptr load(std::string_view name, std::uint32_t flags = 0U); +template +resource_ptr find(std::string_view name); +} // namespace resource + +namespace resource +{ +void hard_cleanup(void); +void soft_cleanup(void); +} // namespace resource + +template +void resource::register_loader(ResourceLoadFunc load_func, ResourceFreeFunc free_func) +{ + resource::detail::register_loader(typeid(T), load_func, free_func); +} + +template +resource_ptr 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(result); +} + +template +resource_ptr resource::find(std::string_view name) +{ + auto result = resource::detail::find_resource(typeid(T), name); + return std::reinterpret_pointer_cast(result); +} -- cgit