#pragma once #include "Utilities/types.h" #include "Utilities/mutex.h" #include #include // Helper namespace namespace id_manager { // Common global mutex extern shared_mutex g_mutex; // ID traits template struct id_traits { static_assert(sizeof(T) == 0, "ID object must specify: id_base, id_step, id_count"); static const u32 base = 1; // First ID (N = 0) static const u32 step = 1; // Any ID: N * id_step + id_base static const u32 count = 65535; // Limit: N < id_count static const u32 invalid = 0; }; template struct id_traits> { static const u32 base = T::id_base; static const u32 step = T::id_step; static const u32 count = T::id_count; static const u32 invalid = base > 0 ? 0 : -1; static_assert(u64{step} * count + base < UINT32_MAX, "ID traits: invalid object range"); }; // Correct usage testing template struct id_verify : std::integral_constant::value> { // If common case, T2 shall be derived from or equal to T }; template struct id_verify> : std::integral_constant::value> { // If T2 contains id_type type, T must be equal to it }; class typeinfo { // Global variable for each registered type template struct registered { static const u32 index; }; // Increment type counter static u32 add_type(u32 i) { static atomic_t g_next{0}; return g_next.fetch_add(i); } public: // Get type index template static inline u32 get_index() { return registered::index; } // Get type count static inline u32 get_count() { return add_type(0); } }; template const u32 typeinfo::registered::index = typeinfo::add_type(1); // ID value with additional type stored class id_key { u32 m_value; // ID value u32 m_type; // True object type public: id_key() = default; id_key(u32 value, u32 type) : m_value(value) , m_type(type) { } u32 value() const { return m_value; } u32 type() const { return m_type; } operator u32() const { return m_value; } }; using id_map = std::vector>>; } // Object manager for emulated process. Multiple objects of specified arbitrary type are given unique IDs. class idm { // Last allocated ID for constructors static thread_local u32 g_id; // Type Index -> ID -> Object. Use global since only one process is supported atm. static std::vector g_map; template static inline u32 get_type() { return id_manager::typeinfo::get_index(); } template static constexpr u32 get_index(u32 id) { return (id - id_manager::id_traits::base) / id_manager::id_traits::step; } // Helper template struct function_traits; template struct function_traits { using object_type = A2; using result_type = R; }; template struct function_traits { using object_type = A2; using result_type = R; }; template struct function_traits { using object_type = A2; using void_type = void; }; template struct function_traits { using object_type = A2; using void_type = void; }; // Helper type: pointer + return value propagated template struct return_pair { std::shared_ptr ptr; RT ret; explicit operator bool() const { return ptr.operator bool(); } T* operator->() const { return ptr.get(); } }; // Unsafe specialization (not refcounted) template struct return_pair { T* ptr; RT ret; explicit operator bool() const { return ptr != nullptr; } T* operator->() const { return ptr; } }; // Prepare new ID (returns nullptr if out of resources) static id_manager::id_map::pointer allocate_id(const id_manager::id_key& info, u32 base, u32 step, u32 count); // Find ID (additionally check type if types are not equal) template static id_manager::id_map::pointer find_id(u32 id) { static_assert(id_manager::id_verify::value, "Invalid ID type combination"); const u32 index = get_index(id); auto& vec = g_map[get_type()]; if (index >= vec.size() || index >= id_manager::id_traits::count) { return nullptr; } auto& data = vec[index]; if (data.second) { if (std::is_same::value || data.first.type() == get_type()) { return &data; } } return nullptr; } // Allocate new ID and assign the object from the provider() template static id_manager::id_map::pointer create_id(F&& provider) { static_assert(id_manager::id_verify::value, "Invalid ID type combination"); // ID info const id_manager::id_key info{get_type(), get_type()}; // ID traits using traits = id_manager::id_traits; // Allocate new id std::lock_guard lock(id_manager::g_mutex); if (auto* place = allocate_id(info, traits::base, traits::step, traits::count)) { // Get object, store it place->second = provider(); if (place->second) { return place; } } return nullptr; } public: // Initialize object manager static void init(); // Remove all objects static void clear(); // Get last ID (updated in create_id/allocate_id) static inline u32 last_id() { return g_id; } // Add a new ID of specified type with specified constructor arguments (returns object or nullptr) template static inline std::enable_if_t::value, std::shared_ptr> make_ptr(Args&&... args) { if (auto pair = create_id([&] { return std::make_shared(std::forward(args)...); })) { return {pair->second, static_cast(pair->second.get())}; } return nullptr; } // Add a new ID of specified type with specified constructor arguments (returns id) template static inline std::enable_if_t::value, u32> make(Args&&... args) { if (auto pair = create_id([&] { return std::make_shared(std::forward(args)...); })) { return pair->first; } return id_manager::id_traits::invalid; } // Add a new ID for an existing object provided (returns new id) template static inline u32 import_existing(const std::shared_ptr& ptr) { if (auto pair = create_id([&] { return ptr; })) { return pair->first; } return id_manager::id_traits::invalid; } // Add a new ID for an object returned by provider() template > static inline u32 import(F&& provider) { if (auto pair = create_id(std::forward(provider))) { return pair->first; } return id_manager::id_traits::invalid; } // Access the ID record without locking (unsafe) template static inline id_manager::id_map::pointer find_unlocked(u32 id) { return find_id(id); } // Check the ID without locking (can be called from other method) template static inline Get* check_unlocked(u32 id) { if (const auto found = find_id(id)) { return static_cast(found->second.get()); } return nullptr; } // Check the ID template static inline Get* check(u32 id) { reader_lock lock(id_manager::g_mutex); return check_unlocked(id); } // Check the ID, access object under shared lock template > static inline auto check(u32 id, F&& func) { reader_lock lock(id_manager::g_mutex); if (const auto ptr = check_unlocked(id)) { if constexpr (!std::is_void_v) { return return_pair{ptr, func(*ptr)}; } else { func(*ptr); return ptr; } } if constexpr (!std::is_void_v) { return return_pair{nullptr}; } else { return static_cast(nullptr); } } // Get the object without locking (can be called from other method) template static inline std::shared_ptr get_unlocked(u32 id) { const auto found = find_id(id); if (UNLIKELY(found == nullptr)) { return nullptr; } return {found->second, static_cast(found->second.get())}; } // Get the object template static inline std::shared_ptr get(u32 id) { reader_lock lock(id_manager::g_mutex); const auto found = find_id(id); if (UNLIKELY(found == nullptr)) { return nullptr; } return {found->second, static_cast(found->second.get())}; } // Get the object, access object under reader lock template > static inline std::conditional_t, std::shared_ptr, return_pair> get(u32 id, F&& func) { reader_lock lock(id_manager::g_mutex); const auto found = find_id(id); if (UNLIKELY(found == nullptr)) { return {nullptr}; } const auto ptr = static_cast(found->second.get()); if constexpr (std::is_void_v) { func(*ptr); return {found->second, ptr}; } else { return {{found->second, ptr}, func(*ptr)}; } } // Access all objects of specified type. Returns the number of objects processed. template ::operator()), typename FRT = typename function_traits::void_type> static inline u32 select(F&& func, int = 0) { static_assert(id_manager::id_verify::value, "Invalid ID type combination"); reader_lock lock(id_manager::g_mutex); u32 result = 0; for (auto& id : g_map[get_type()]) { if (id.second) { if (std::is_same::value || id.first.type() == get_type()) { func(id.first, *static_cast::object_type*>(id.second.get())); result++; } } } return result; } // Access all objects of specified type. If function result evaluates to true, stop and return the object and the value. template ::operator()), typename FRT = typename function_traits::result_type> static inline auto select(F&& func) { static_assert(id_manager::id_verify::value, "Invalid ID type combination"); using object_type = typename function_traits::object_type; using result_type = return_pair; reader_lock lock(id_manager::g_mutex); for (auto& id : g_map[get_type()]) { if (auto ptr = static_cast(id.second.get())) { if (std::is_same::value || id.first.type() == get_type()) { if (FRT result = func(id.first, *ptr)) { return result_type{{id.second, ptr}, std::move(result)}; } } } } return result_type{nullptr}; } // Remove the ID template static inline bool remove(u32 id) { std::shared_ptr ptr; { std::lock_guard lock(id_manager::g_mutex); if (const auto found = find_id(id)) { ptr = std::move(found->second); } else { return false; } } return true; } // Remove the ID and return the object template static inline std::shared_ptr withdraw(u32 id) { std::shared_ptr ptr; { std::lock_guard lock(id_manager::g_mutex); if (const auto found = find_id(id)) { ptr = std::move(found->second); } else { return nullptr; } } return {ptr, static_cast(ptr.get())}; } // Remove the ID after accessing the object under writer lock, return the object and propagate return value template > static inline std::conditional_t, std::shared_ptr, return_pair> withdraw(u32 id, F&& func) { std::unique_lock lock(id_manager::g_mutex); if (const auto found = find_id(id)) { const auto _ptr = static_cast(found->second.get()); if constexpr (std::is_void_v) { func(*_ptr); std::shared_ptr ptr = std::move(found->second); return {ptr, static_cast(ptr.get())}; } else { FRT ret = func(*_ptr); if (ret) { // If return value evaluates to true, don't delete the object (error code) return {{found->second, _ptr}, std::move(ret)}; } std::shared_ptr ptr = std::move(found->second); return {{ptr, static_cast(ptr.get())}, std::move(ret)}; } } return {nullptr}; } }; // Object manager for emulated process. One unique object per type, or zero. class fxm { // Type Index -> Object. Use global since only one process is supported atm. static std::vector> g_vec; template static inline u32 get_type() { return id_manager::typeinfo::get_index(); } public: // Initialize object manager static void init(); // Remove all objects static void clear(); // Create the object (returns nullptr if it already exists) template static std::enable_if_t::value, std::shared_ptr> make(Args&&... args) { std::shared_ptr ptr; { std::lock_guard lock(id_manager::g_mutex); auto& cur = g_vec[get_type()]; if (!cur) { ptr = std::make_shared(std::forward(args)...); cur = ptr; } else { return nullptr; } } return ptr; } // Create the object unconditionally (old object will be removed if it exists) template static std::enable_if_t::value, std::shared_ptr> make_always(Args&&... args) { std::shared_ptr ptr; std::shared_ptr old; { std::lock_guard lock(id_manager::g_mutex); auto& cur = g_vec[get_type()]; ptr = std::make_shared(std::forward(args)...); old = std::move(cur); cur = ptr; } return ptr; } // Emplace the object returned by provider() and return it if no object exists template static auto import(F&& provider, Args&&... args) -> decltype(static_cast>(provider(std::forward(args)...))) { std::shared_ptr ptr; { std::lock_guard lock(id_manager::g_mutex); auto& cur = g_vec[get_type()]; if (!cur) { ptr = provider(std::forward(args)...); if (ptr) { cur = ptr; } } if (!ptr) { return nullptr; } } return ptr; } // Emplace the object return by provider() (old object will be removed if it exists) template static auto import_always(F&& provider, Args&&... args) -> decltype(static_cast>(provider(std::forward(args)...))) { std::shared_ptr ptr; std::shared_ptr old; { std::lock_guard lock(id_manager::g_mutex); auto& cur = g_vec[get_type()]; ptr = provider(std::forward(args)...); if (ptr) { old = std::move(cur); cur = ptr; } else { return nullptr; } } return ptr; } // Get the object unconditionally (create an object if it doesn't exist) template static std::enable_if_t::value, std::shared_ptr> get_always(Args&&... args) { std::shared_ptr ptr; { std::lock_guard lock(id_manager::g_mutex); auto& old = g_vec[get_type()]; if (old) { return {old, static_cast(old.get())}; } else { ptr = std::make_shared(std::forward(args)...); old = ptr; } } return ptr; } // Unsafe version of check(), can be used in some cases template static inline T* check_unlocked() { return static_cast(g_vec[get_type()].get()); } // Check whether the object exists template static inline T* check() { reader_lock lock(id_manager::g_mutex); return check_unlocked(); } // Get the object (returns nullptr if it doesn't exist) template static inline std::shared_ptr get() { reader_lock lock(id_manager::g_mutex); auto& ptr = g_vec[get_type()]; return {ptr, static_cast(ptr.get())}; } // Delete the object template static inline bool remove() { std::shared_ptr ptr; { std::lock_guard lock(id_manager::g_mutex); ptr = std::move(g_vec[get_type()]); } return ptr.operator bool(); } // Delete the object and return it template static inline std::shared_ptr withdraw() { std::shared_ptr ptr; { std::lock_guard lock(id_manager::g_mutex); ptr = std::move(g_vec[get_type()]); } return {ptr, static_cast(ptr.get())}; } }; #include "Utilities/typemap.h" extern utils::typemap g_typemap; constexpr utils::typemap* g_idm = &g_typemap; using utils::id_new; using utils::id_any; using utils::id_always;