diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 4c6b604acf..22c9fd1690 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -456,6 +456,7 @@ target_sources(rpcs3_emu PRIVATE RSX/Overlays/overlay_edit_text.cpp RSX/Overlays/overlay_fonts.cpp RSX/Overlays/overlay_list_view.cpp + RSX/Overlays/overlay_manager.cpp RSX/Overlays/overlay_media_list_dialog.cpp RSX/Overlays/overlay_message.cpp RSX/Overlays/overlay_message_dialog.cpp diff --git a/rpcs3/Emu/RSX/Overlays/overlay_manager.cpp b/rpcs3/Emu/RSX/Overlays/overlay_manager.cpp new file mode 100644 index 0000000000..e5103a57dc --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_manager.cpp @@ -0,0 +1,157 @@ +#include "stdafx.h" +#include "overlay_manager.h" + +namespace rsx +{ + namespace overlays + { + void display_manager::lock() + { + m_list_mutex.lock_shared(); + } + + void display_manager::unlock() + { + m_list_mutex.unlock_shared(); + + if (!m_uids_to_remove.empty() || !m_type_ids_to_remove.empty()) + { + std::lock_guard lock(m_list_mutex); + cleanup_internal(); + } + } + + std::shared_ptr display_manager::get(u32 uid) + { + reader_lock lock(m_list_mutex); + + for (const auto& iface : m_iface_list) + { + if (iface->uid == uid) + return iface; + } + + return {}; + } + + void display_manager::remove(u32 uid) + { + if (m_list_mutex.try_lock()) + { + remove_uid(uid); + m_list_mutex.unlock(); + } + else + { + m_uids_to_remove.push_back(uid); + } + } + + void display_manager::dispose(const std::vector& uids) + { + std::lock_guard lock(m_list_mutex); + + if (!m_uids_to_remove.empty() || !m_type_ids_to_remove.empty()) + { + cleanup_internal(); + } + + m_dirty_list.erase + ( + std::remove_if(m_dirty_list.begin(), m_dirty_list.end(), [&uids](std::shared_ptr& e) + { + return std::find(uids.begin(), uids.end(), e->uid) != uids.end(); + }), + m_dirty_list.end()); + } + + bool display_manager::remove_type(u32 type_id) + { + bool found = false; + for (auto It = m_iface_list.begin(); It != m_iface_list.end();) + { + if (It->get()->type_index == type_id) + { + on_overlay_removed(*It); + m_dirty_list.push_back(std::move(*It)); + It = m_iface_list.erase(It); + found = true; + } + else + { + ++It; + } + } + + return found; + } + + bool display_manager::remove_uid(u32 uid) + { + for (auto It = m_iface_list.begin(); It != m_iface_list.end(); It++) + { + const auto e = It->get(); + if (e->uid == uid) + { + on_overlay_removed(*It); + m_dirty_list.push_back(std::move(*It)); + m_iface_list.erase(It); + return true; + } + } + + return false; + } + + void display_manager::cleanup_internal() + { + for (const auto& uid : m_uids_to_remove) + { + remove_uid(uid); + } + + for (const auto& type_id : m_type_ids_to_remove) + { + remove_type(type_id); + } + + m_uids_to_remove.clear(); + m_type_ids_to_remove.clear(); + } + + void display_manager::on_overlay_activated(const std::shared_ptr& item) + { + if (auto iface = std::dynamic_pointer_cast(item)) + { + std::lock_guard lock(m_input_thread_lock); + m_input_token_stack.emplace_front(std::move(iface)); + } + } + + void display_manager::on_overlay_removed(const std::shared_ptr& item) + { + if (!dynamic_cast(item.get())) + { + // Not instance of UI, ignore + return; + } + + std::lock_guard lock(m_input_thread_lock); + for (auto& entry : m_input_token_stack) + { + if (entry.target->uid == item->uid) + { + // Release + entry.target = {}; + break; + } + } + + // The top must never be an empty ref. Pop all empties. + while (!m_input_token_stack.front().target && m_input_token_stack.size()) + { + m_input_token_stack.pop_front(); + } + } + } +} diff --git a/rpcs3/Emu/RSX/Overlays/overlay_manager.h b/rpcs3/Emu/RSX/Overlays/overlay_manager.h new file mode 100644 index 0000000000..ec50d3777e --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_manager.h @@ -0,0 +1,170 @@ +#pragma once + +#include "overlays.h" + +#include "Emu/IdManager.h" +#include "Utilities/mutex.h" +#include "Utilities/Timer.h" + +#include +#include + +namespace rsx +{ + namespace overlays + { + struct overlay; + + class display_manager + { + private: + atomic_t m_uid_ctr = 0; + std::vector> m_iface_list; + std::vector> m_dirty_list; + + shared_mutex m_list_mutex; + std::vector m_uids_to_remove; + std::vector m_type_ids_to_remove; + + bool remove_type(u32 type_id); + + bool remove_uid(u32 uid); + + void cleanup_internal(); + + void on_overlay_activated(const std::shared_ptr& item); + + void on_overlay_removed(const std::shared_ptr& item); + + public: + // Disable default construction to make it conditionally available in g_fxo + explicit display_manager(int) noexcept + {} + + // Adds an object to the internal list. Optionally removes other objects of the same type. + // Original handle loses ownership but a usable pointer is returned + template + std::shared_ptr add(std::shared_ptr& entry, bool remove_existing = true) + { + std::lock_guard lock(m_list_mutex); + + entry->uid = m_uid_ctr.fetch_add(1); + entry->type_index = id_manager::typeinfo::get_index(); + + if (remove_existing) + { + for (auto It = m_iface_list.begin(); It != m_iface_list.end(); It++) + { + if (It->get()->type_index == entry->type_index) + { + // Replace + m_dirty_list.push_back(std::move(*It)); + *It = std::move(entry); + return std::static_pointer_cast(*It); + } + } + } + + m_iface_list.push_back(std::move(entry)); + on_overlay_activated(m_iface_list.back()); + return std::static_pointer_cast(m_iface_list.back()); + } + + // Allocates object and adds to internal list. Returns pointer to created object + template + std::shared_ptr create(Args&&... args) + { + auto object = std::make_shared(std::forward(args)...); + return add(object); + } + + // Removes item from list if it matches the uid + void remove(u32 uid); + + // Removes all objects of this type from the list + template + void remove() + { + const auto type_id = id_manager::typeinfo::get_index(); + if (m_list_mutex.try_lock()) + { + remove_type(type_id); + m_list_mutex.unlock(); + } + else + { + m_type_ids_to_remove.push_back(type_id); + } + } + + // True if any visible elements to draw exist + bool has_visible() const + { + return !m_iface_list.empty(); + } + + // True if any elements have been deleted but their resources may not have been cleaned up + bool has_dirty() const + { + return !m_dirty_list.empty(); + } + + // Returns current list for reading. Caller must ensure synchronization by first locking the list + const std::vector>& get_views() const + { + return m_iface_list; + } + + // Returns current list of removed objects not yet deallocated for reading. + // Caller must ensure synchronization by first locking the list + const std::vector>& get_dirty() const + { + return m_dirty_list; + } + + // Deallocate object. Object must first be removed via the remove() functions + void dispose(const std::vector& uids); + + // Returns pointer to the object matching the given uid + std::shared_ptr get(u32 uid); + + // Returns pointer to the first object matching the given type + template + std::shared_ptr get() + { + reader_lock lock(m_list_mutex); + + const auto type_id = id_manager::typeinfo::get_index(); + for (const auto& iface : m_iface_list) + { + if (iface->type_index == type_id) + { + return std::static_pointer_cast(iface); + } + } + + return {}; + } + + // Lock for read-only access (BasicLockable) + void lock(); + + // Release read-only lock (BasicLockable). May perform internal cleanup before returning + void unlock(); + + private: + struct overlay_input_thread + { + static constexpr auto thread_name = "Overlay Input Thread"sv; + }; + + struct input_thread_access_token + { + std::shared_ptr target; + }; + + std::deque m_input_token_stack; + shared_mutex m_input_thread_lock; + }; + } +} diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index f3aa78a029..c3d2018dc3 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -95,6 +95,7 @@ + @@ -555,6 +556,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index ee3c6e8de8..c6b12bb721 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1144,6 +1144,9 @@ Emu\NP + + Emu\GPU\RSX\Overlays + @@ -2296,6 +2299,9 @@ Emu\GPU\RSX\Overlays\HomeMenu + + Emu\GPU\RSX\Overlays + Emu\NP