129 lines
4.6 KiB
C++
129 lines
4.6 KiB
C++
#include <luma/assets.hpp>
|
|
|
|
#include <cassert>
|
|
#include <condition_variable>
|
|
#include <cstdint>
|
|
#include <expected>
|
|
#include <exception>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <utility>
|
|
|
|
#include <modulus.hpp>
|
|
#include <modulus/log.hpp>
|
|
|
|
#include <luma/detail/errors.hpp>
|
|
|
|
|
|
struct luma::asset_cache::in_flight_load {
|
|
std::condition_variable ready{};
|
|
std::thread::id owner{};
|
|
std::expected<std::shared_ptr<luma::texture>, modulus::error> result{};
|
|
std::bool_t done{false};
|
|
};
|
|
|
|
luma::asset_cache::asset_cache(luma::renderer_resources &resources)
|
|
: resources_{&resources} {
|
|
assert(resources_ != nullptr);
|
|
}
|
|
|
|
auto luma::asset_cache::texture(std::string const &path) -> std::expected<std::shared_ptr<luma::texture>, modulus::error> {
|
|
assert(resources_ != nullptr);
|
|
if (path.empty()) {
|
|
return luma::detail::fail<std::shared_ptr<luma::texture>>(luma::detail::error_invalid_argument, "texture path must not be empty");
|
|
}
|
|
auto generation = std::uint64_t{};
|
|
auto loading = std::shared_ptr<luma::asset_cache::in_flight_load>{};
|
|
{
|
|
auto lock = std::unique_lock{mutex_};
|
|
if (auto found = textures_.find(path); found != textures_.end()) {
|
|
assert(found->second != nullptr);
|
|
return modulus::success(found->second);
|
|
}
|
|
if (auto found = loading_.find(path); found != loading_.end()) {
|
|
loading = found->second;
|
|
assert(loading != nullptr);
|
|
if (loading->owner == std::this_thread::get_id()) {
|
|
return luma::detail::fail<std::shared_ptr<luma::texture>>(luma::detail::error_unavailable, "texture load reentered for path: " + path);
|
|
}
|
|
loading->ready.wait(lock, [&loading] {
|
|
return loading->done;
|
|
});
|
|
if (auto cached = textures_.find(path); cached != textures_.end()) {
|
|
assert(cached->second != nullptr);
|
|
return modulus::success(cached->second);
|
|
}
|
|
if (!loading->result) {
|
|
return modulus::fail<std::shared_ptr<luma::texture>>(loading->result.error());
|
|
}
|
|
assert(*loading->result != nullptr);
|
|
return modulus::success(*loading->result);
|
|
}
|
|
loading = std::make_shared<luma::asset_cache::in_flight_load>();
|
|
loading->owner = std::this_thread::get_id();
|
|
loading_.emplace(path, loading);
|
|
generation = generation_;
|
|
}
|
|
auto loaded = std::expected<std::shared_ptr<luma::texture>, modulus::error>{};
|
|
modulus::log_debug("luma texture loading: " + path);
|
|
try {
|
|
loaded = resources_->load_texture(path);
|
|
} catch (std::exception const &error) {
|
|
loaded = luma::detail::fail<std::shared_ptr<luma::texture>>(luma::detail::error_internal, std::string{"texture load failed: "} + error.what());
|
|
} catch (...) {
|
|
loaded = luma::detail::fail<std::shared_ptr<luma::texture>>(luma::detail::error_internal, "texture load failed with an unknown exception");
|
|
}
|
|
if (!loaded) {
|
|
modulus::log_debug("luma texture load failed: " + path);
|
|
auto lock = std::scoped_lock{mutex_};
|
|
loading->result = modulus::fail<std::shared_ptr<luma::texture>>(loaded.error());
|
|
loading->done = true;
|
|
loading_.erase(path);
|
|
loading->ready.notify_all();
|
|
return loaded;
|
|
}
|
|
if (*loaded == nullptr) {
|
|
modulus::log_debug("luma texture load returned null: " + path);
|
|
auto result = luma::detail::fail<std::shared_ptr<luma::texture>>(luma::detail::error_not_found, "texture load returned no texture: " + path);
|
|
auto lock = std::scoped_lock{mutex_};
|
|
loading->result = modulus::fail<std::shared_ptr<luma::texture>>(result.error());
|
|
loading->done = true;
|
|
loading_.erase(path);
|
|
loading->ready.notify_all();
|
|
return result;
|
|
}
|
|
auto texture = *loaded;
|
|
auto lock = std::scoped_lock{mutex_};
|
|
if (auto found = textures_.find(path); found != textures_.end()) {
|
|
assert(found->second != nullptr);
|
|
loading->result = modulus::success(found->second);
|
|
loading->done = true;
|
|
loading_.erase(path);
|
|
loading->ready.notify_all();
|
|
return modulus::success(found->second);
|
|
}
|
|
if (generation != generation_) {
|
|
loading->result = modulus::success(texture);
|
|
loading->done = true;
|
|
loading_.erase(path);
|
|
loading->ready.notify_all();
|
|
return modulus::success(std::move(texture));
|
|
}
|
|
auto inserted = textures_.emplace(path, std::move(texture));
|
|
assert(inserted.first->second != nullptr);
|
|
loading->result = modulus::success(inserted.first->second);
|
|
loading->done = true;
|
|
loading_.erase(path);
|
|
loading->ready.notify_all();
|
|
return modulus::success(inserted.first->second);
|
|
}
|
|
|
|
auto luma::asset_cache::clear() -> void {
|
|
assert(resources_ != nullptr);
|
|
auto lock = std::scoped_lock{mutex_};
|
|
++generation_;
|
|
textures_.clear();
|
|
}
|