#ifndef MATADOR_OBJECT_CACHE_HPP #define MATADOR_OBJECT_CACHE_HPP #include "matador/object/object_proxy.hpp" #include "matador/object/object_resolver.hpp" #include "matador/utils/identifier.hpp" #include #include #include #include #include #include namespace matador::object { struct cache_entry_base { virtual ~cache_entry_base() = default; /** * @brief Gibt an, ob der Eintrag vollständig "tot" ist (weder Proxy noch Entity leben). * * Wird von @ref object_cache::sweep() verwendet, um Einträge ohne T-Kenntnis zu bereinigen. */ [[nodiscard]] virtual bool is_dead() const noexcept = 0; }; template struct cache_entry : cache_entry_base { std::weak_ptr > proxy; std::weak_ptr entity; [[nodiscard]] bool is_dead() const noexcept override { return proxy.expired() && entity.expired(); } }; /** * @brief Thread-sicherer Cache für Objekt-Proxies und (optional) geladene Entities. * * Der Cache verwaltet Einträge pro Kombination aus Objekt-Typ @p T und Primärschlüssel/Identifier. * Für jede (T, id)-Kombination können sowohl * - ein Proxy (@c object_proxy) als auch * - die dazugehörige Entity (@c T) * zwischengespeichert werden. * * Der Cache hält dabei nur @c std::weak_ptr auf Proxy und Entity, d.h. er verlängert deren Lebensdauer nicht. * * @note Thread-Safety: Alle öffentlichen Methoden sind threadsafe (intern synchronisiert). * @note Semantik: Ein Eintrag kann aus dem Cache opportunistisch entfernt werden, sobald Proxy und Entity abgelaufen sind. */ class object_cache { public: /** * @brief Erzeugt einen leeren Cache. */ object_cache() = default; /** * @brief Liefert einen Proxy für (T, id) und erstellt ihn bei Bedarf. * * Falls bereits ein lebender Proxy im Cache existiert, wird er wiederverwendet. * Andernfalls wird ein neuer Proxy erzeugt, im Cache abgelegt und zurückgegeben. * Falls für (T, id) bereits eine Entity vorhanden ist, wird sie an den Proxy gebunden. * * @tparam T Entity-Typ. * @tparam ResolverPtr Zeiger-Typ auf einen Resolver, typischerweise * @c std::shared_ptr> oder @c std::weak_ptr>. * @param id Identifier/Primärschlüssel der Entity. * @param resolver_ptr Resolver (shared/weak), der zur Lazy-Auflösung durch den Proxy genutzt wird. * @return Shared-Pointer auf den (ggf. neu erstellten) Proxy. * * @note Diese Methode ist threadsafe. */ template std::shared_ptr> acquire_proxy(utils::identifier id, ResolverPtr &&resolver_ptr) { const auto k = make_key(id); std::unique_lock lock(mutex_); auto it = map_.find(k); if (it != map_.end()) { // found entry, return std::shared_ptr of proxy auto *entry = entry_cast_(it->second.get()); if (auto proxy_ptr = entry->proxy.lock()) { return proxy_ptr; } // if the proxy is dead, but the entity is alive, create a new proxy and attach entity auto weak_resolver = to_weak(std::forward(resolver_ptr)); auto proxy_ptr = std::make_shared>(weak_resolver, id); if (auto obj = entry->entity.lock()) { proxy_ptr->attach(std::move(obj)); } entry->proxy = proxy_ptr; // return the shared_ptr of the proxy return proxy_ptr; } // entry couldn't be found, create a new entry auto entry_ptr = std::make_unique>(); auto *entry = entry_ptr.get(); // create a weak resolver and shared proxy auto weak_resolver = to_weak(std::forward(resolver_ptr)); auto proxy_ptr = std::make_shared>(weak_resolver, id); // lock entity and attach to proxy if (auto obj = entry->entity.lock()) { proxy_ptr->attach(std::move(obj)); } // set the new proxy into cache entry entry->proxy = proxy_ptr; map_.emplace(k, std::move(entry_ptr)); // return the shared_ptr of the proxy return proxy_ptr; } /** * @brief Verknüpft eine geladene Entity mit einem Cache-Eintrag (Type, id). * * Speichert die Entity als @c weak_ptr im Cache. Falls bereits ein Proxy existiert, * wird die Entity sofort an den Proxy gebunden. * * @tparam Type Entity-Typ. * @param id Identifier/Primärschlüssel der Entity. * @param obj Geladene Entity als @c shared_ptr. * * @note Diese Methode ist threadsafe. */ template void attach_entity(const utils::identifier &id, std::shared_ptr obj) { const auto k = make_key(id); std::unique_lock lock(mutex_); auto it = map_.find(k); if (it == map_.end()) { auto entry_ptr = std::make_unique>(); auto *entry = entry_ptr.get(); entry->entity = obj; map_.emplace(k, std::move(entry_ptr)); return; } auto *entry = entry_cast_(it->second.get()); entry->entity = obj; if (auto proxy = entry->proxy.lock()) { proxy->attach(std::move(obj)); } prune_if_dead(it); } /** * @brief Liefert die aktuell geladene Entity für (Type, id), falls vorhanden. * * @tparam Type Entity-Typ. * @param id Identifier/Primärschlüssel der Entity. * @return @c shared_ptr auf die Entity oder ein leerer Pointer, falls nicht geladen/abgelaufen. * * @note Diese Methode ist threadsafe. */ template std::shared_ptr get_entity(const utils::identifier &id) { const auto k = make_key(id); std::unique_lock lock(mutex_); auto it = map_.find(k); if (it == map_.end()) { return {}; } auto *entry = entry_cast_(it->second.get()); auto entity_ptr = entry->entity.lock(); prune_if_dead(it); return entity_ptr; } /** * @brief Prüft, ob für (Type, id) aktuell eine lebende Entity verfügbar ist. * * @tparam Type Entity-Typ. * @param id Identifier/Primärschlüssel der Entity. * @return @c true, wenn die Entity-Referenz nicht abgelaufen ist; sonst @c false. * * @note Diese Methode ist threadsafe. */ template bool is_loaded(const utils::identifier &id) { const auto k = make_key(id); std::unique_lock lock(mutex_); auto it = map_.find(k); if (it == map_.end()) { return false; } auto *entry = entry_cast_(it->second.get()); const bool loaded = !entry->entity.expired(); prune_if_dead(it); return loaded; } template void erase(utils::identifier id) { const auto k = make_key(id); std::unique_lock lock(mutex_); auto it = map_.find(k); if (it == map_.end()) { return; } auto *e = entry_cast_(it->second.get()); if (auto p = e->proxy.lock()) { p->invalidate(); } map_.erase(it); } /** * @brief Führt einen Cleanup-Lauf aus und entfernt Einträge, deren Proxy und Entity abgelaufen sind. * * @return Anzahl der entfernten Einträge. * * @note Diese Methode ist threadsafe. */ [[nodiscard]] std::size_t sweep() { std::unique_lock lock(mutex_); std::size_t removed = 0; for (auto it = map_.begin(); it != map_.end(); ) { if (it->second->is_dead()) { it = map_.erase(it); ++removed; } else { ++it; } } return removed; } private: struct key { std::type_index type; utils::identifier id{}; bool operator==(key const &other) const { return type == other.type && id == other.id; } }; struct key_hash { size_t operator()(key const &k) const noexcept { // (3) robustere Hash-Kombination als XOR size_t seed = std::hash()(k.type); const size_t h2 = std::hash()(k.id); seed ^= h2 + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); return seed; } }; template static key make_key(const utils::identifier &id) { return key{std::type_index(typeid(T)), id}; } // (4) Debug-Absicherung für type-erased Casts template static cache_entry *entry_cast_(cache_entry_base *base) { #ifndef NDEBUG auto *p = dynamic_cast *>(base); assert(p && "object_cache: Type mismatch in cache entry (key/type invariant violated)"); return p; #else return static_cast *>(base); #endif } template using enable_if_resolver_shared_ptr_t = std::enable_if_t, U>, int>; template using enable_if_resolver_weak_ptr_t = std::enable_if_t, U>, int>; // shared_ptr -> weak_ptr template = 0> static std::weak_ptr> to_weak(const std::shared_ptr &s) { return std::weak_ptr>(std::static_pointer_cast>(s)); } // weak_ptr -> weak_ptr template = 0> static std::weak_ptr> to_weak(const std::weak_ptr &w) { return std::weak_ptr>(w); } // (2) Opportunistischer Cleanup: entferne Entry, wenn beide weak_ptr abgelaufen sind. template bool prune_if_dead(It &it) { if (it == map_.end()) { return false; } if (it->second && it->second->is_dead()) { map_.erase(it); it = map_.end(); return true; } return false; } template void prune_if_dead_typed(typename std::unordered_map, key_hash>::iterator &it) { (void)T{}; // T bleibt im Interface, damit bestehende Call-Sites unverändert bleiben können. prune_if_dead(it); } private: mutable std::shared_mutex mutex_{}; std::unordered_map, key_hash> map_; }; // --- Inline Anpassungen: typed prune nach Zugriff (damit wir T haben) --- // ... existing code ... // (Die typed prune wird oben benutzt; wir rufen sie direkt im Codepfad anstelle von prune_if_dead_) } // namespace matador::object #endif //MATADOR_OBJECT_CACHE_HPP