diff --git a/pcsx2-qt/Debugger/DebuggerWidget.cpp b/pcsx2-qt/Debugger/DebuggerWidget.cpp index d1fd796e92..218c7cfd87 100644 --- a/pcsx2-qt/Debugger/DebuggerWidget.cpp +++ b/pcsx2-qt/Debugger/DebuggerWidget.cpp @@ -14,6 +14,7 @@ DebuggerWidget::DebuggerWidget(const DebuggerWidgetParameters& parameters, u32 flags) : QWidget(parameters.parent) + , m_id(parameters.id) , m_unique_name(parameters.unique_name) , m_cpu(parameters.cpu) , m_cpu_override(parameters.cpu_override) @@ -36,6 +37,11 @@ QString DebuggerWidget::uniqueName() const return m_unique_name; } +u64 DebuggerWidget::id() const +{ + return m_id; +} + QString DebuggerWidget::displayName() const { QString name = displayNameWithoutSuffix(); diff --git a/pcsx2-qt/Debugger/DebuggerWidget.h b/pcsx2-qt/Debugger/DebuggerWidget.h index 5be16b014e..a17695198b 100644 --- a/pcsx2-qt/Debugger/DebuggerWidget.h +++ b/pcsx2-qt/Debugger/DebuggerWidget.h @@ -16,6 +16,7 @@ class JsonValueWrapper; struct DebuggerWidgetParameters { QString unique_name; + u64 id = 0; DebugInterface* cpu = nullptr; std::optional cpu_override; QWidget* parent = nullptr; @@ -28,6 +29,7 @@ class DebuggerWidget : public QWidget public: QString uniqueName() const; + u64 id() const; // Get the translated name that should be displayed for this widget. QString displayName() const; @@ -177,6 +179,12 @@ private: const char* action_prefix, std::function event_func); + // Used for sorting debugger widgets that have the same display name. Unique + // within a single layout. + u64 m_id; + + // Identifier for the dock widget used by KDDockWidgets. Unique within a + // single layout. QString m_unique_name; // A user-defined name, or an empty string if no name was specified so that diff --git a/pcsx2-qt/Debugger/Docking/DockLayout.cpp b/pcsx2-qt/Debugger/Docking/DockLayout.cpp index 348f7f4c8b..ea73f30bf5 100644 --- a/pcsx2-qt/Debugger/Docking/DockLayout.cpp +++ b/pcsx2-qt/Debugger/Docking/DockLayout.cpp @@ -29,7 +29,7 @@ const char* DEBUGGER_LAYOUT_FILE_FORMAT = "PCSX2 Debugger User Interface Layout"; // Increment this whenever there is a breaking change to the JSON format. -const u32 DEBUGGER_LAYOUT_FILE_VERSION_MAJOR = 1; +const u32 DEBUGGER_LAYOUT_FILE_VERSION_MAJOR = 2; // Increment this whenever there is a non-breaking change to the JSON format. const u32 DEBUGGER_LAYOUT_FILE_VERSION_MINOR = 0; @@ -70,7 +70,7 @@ DockLayout::DockLayout( : m_name(name) , m_cpu(cpu) , m_is_default(is_default) - , m_next_unique_name(layout_to_clone.m_next_unique_name) + , m_next_id(layout_to_clone.m_next_id) , m_base_layout(layout_to_clone.m_base_layout) , m_toolbars(layout_to_clone.m_toolbars) , m_geometry(layout_to_clone.m_geometry) @@ -83,6 +83,7 @@ DockLayout::DockLayout( DebuggerWidgetParameters parameters; parameters.unique_name = unique_name; + parameters.id = widget_to_clone->id(); parameters.cpu = &DebugInterface::get(cpu); parameters.cpu_override = widget_to_clone->cpuOverride(); @@ -262,7 +263,7 @@ void DockLayout::reset() delete widget; } - m_next_unique_name = 0; + m_next_id = 0; m_toolbars.clear(); m_widgets.clear(); m_geometry.clear(); @@ -278,7 +279,8 @@ void DockLayout::reset() const DockTables::DebuggerWidgetDescription& dock_description = iterator->second; DebuggerWidgetParameters parameters; - parameters.unique_name = generateNewUniqueName(base_layout->widgets[i].type.c_str()); + std::tie(parameters.unique_name, parameters.id) = + generateNewUniqueName(base_layout->widgets[i].type.c_str()); parameters.cpu = &DebugInterface::get(m_cpu); if (parameters.unique_name.isEmpty()) @@ -330,7 +332,7 @@ void DockLayout::updateDockWidgetTitles() { std::sort(widgets.begin(), widgets.end(), [&](const DebuggerWidget* lhs, const DebuggerWidget* rhs) { - return lhs->uniqueName() < rhs->uniqueName(); + return lhs->id() < rhs->id(); }); for (size_t i = 0; i < widgets.size(); i++) @@ -389,7 +391,7 @@ void DockLayout::createDebuggerWidget(const std::string& type) const DockTables::DebuggerWidgetDescription& description = description_iterator->second; DebuggerWidgetParameters parameters; - parameters.unique_name = generateNewUniqueName(type.c_str()); + std::tie(parameters.unique_name, parameters.id) = generateNewUniqueName(type.c_str()); parameters.cpu = &DebugInterface::get(m_cpu); if (parameters.unique_name.isEmpty()) @@ -428,6 +430,7 @@ void DockLayout::recreateDebuggerWidget(const QString& unique_name) DebuggerWidgetParameters parameters; parameters.unique_name = old_debugger_widget->uniqueName(); + parameters.id = old_debugger_widget->id(); parameters.cpu = &DebugInterface::get(m_cpu); parameters.cpu_override = old_debugger_widget->cpuOverride(); @@ -545,24 +548,22 @@ bool DockLayout::save(DockLayout::Index layout_index) rapidjson::Document geometry; const char* cpu_name = DebugInterface::cpuName(m_cpu); - const std::string& default_layouts_hash = DockTables::hashDefaultLayouts(); + u32 default_layout_hash = DockTables::hashDefaultLayouts(); rapidjson::Value format; format.SetString(DEBUGGER_LAYOUT_FILE_FORMAT, strlen(DEBUGGER_LAYOUT_FILE_FORMAT)); json.AddMember("format", format, json.GetAllocator()); - json.AddMember("version_major", DEBUGGER_LAYOUT_FILE_VERSION_MAJOR, json.GetAllocator()); - json.AddMember("version_minor", DEBUGGER_LAYOUT_FILE_VERSION_MINOR, json.GetAllocator()); - rapidjson::Value version_hash; - version_hash.SetString(default_layouts_hash.c_str(), default_layouts_hash.size()); - json.AddMember("version_hash", version_hash, json.GetAllocator()); + json.AddMember("versionMajor", DEBUGGER_LAYOUT_FILE_VERSION_MAJOR, json.GetAllocator()); + json.AddMember("versionMinor", DEBUGGER_LAYOUT_FILE_VERSION_MINOR, json.GetAllocator()); + json.AddMember("defaultLayoutHash", default_layout_hash, json.GetAllocator()); std::string name_str = m_name.toStdString(); json.AddMember("name", rapidjson::Value().SetString(name_str.c_str(), name_str.size()), json.GetAllocator()); json.AddMember("target", rapidjson::Value().SetString(cpu_name, strlen(cpu_name)), json.GetAllocator()); json.AddMember("index", static_cast(layout_index), json.GetAllocator()); json.AddMember("isDefault", m_is_default, json.GetAllocator()); - json.AddMember("nextUniqueName", m_next_unique_name, json.GetAllocator()); + json.AddMember("nextId", m_next_id, json.GetAllocator()); if (!m_base_layout.empty()) { @@ -590,6 +591,7 @@ bool DockLayout::save(DockLayout::Index layout_index) rapidjson::Value name; name.SetString(name_str.c_str(), name_str.size(), json.GetAllocator()); object.AddMember("uniqueName", name, json.GetAllocator()); + object.AddMember("id", widget->id(), json.GetAllocator()); const char* type_str = widget->metaObject()->className(); rapidjson::Value type; @@ -687,11 +689,11 @@ void DockLayout::load( return; } - auto version_major = json.FindMember("version_major"); + auto version_major = json.FindMember("versionMajor"); if (version_major == json.MemberEnd() || !version_major->value.IsInt()) { - Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_major' property.", path.c_str()); - result = INVALID_FORMAT; + Console.Error("Debugger: Layout file '%s' has missing or invalid 'versionMajor' property.", path.c_str()); + result = MAJOR_VERSION_MISMATCH; return; } @@ -701,23 +703,23 @@ void DockLayout::load( return; } - auto version_minor = json.FindMember("version_minor"); + auto version_minor = json.FindMember("versionMinor"); if (version_minor == json.MemberEnd() || !version_minor->value.IsInt()) { - Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_minor' property.", path.c_str()); - result = INVALID_FORMAT; + Console.Error("Debugger: Layout file '%s' has missing or invalid 'versionMinor' property.", path.c_str()); + result = MAJOR_VERSION_MISMATCH; return; } - auto version_hash = json.FindMember("version_hash"); - if (version_hash == json.MemberEnd() || !version_hash->value.IsString()) + auto default_layout_hash = json.FindMember("defaultLayoutHash"); + if (default_layout_hash == json.MemberEnd() || !default_layout_hash->value.IsUint()) { - Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_hash' property.", path.c_str()); - result = INVALID_FORMAT; + Console.Error("Debugger: Layout file '%s' has missing or invalid 'defaultLayoutHash' property.", path.c_str()); + result = MAJOR_VERSION_MISMATCH; return; } - if (strcmp(version_hash->value.GetString(), DockTables::hashDefaultLayouts().c_str()) != 0) + if (default_layout_hash->value.GetUint() != DockTables::hashDefaultLayouts()) result = DEFAULT_LAYOUT_HASH_MISMATCH; auto name = json.FindMember("name"); @@ -745,9 +747,9 @@ void DockLayout::load( if (is_default != json.MemberEnd() && is_default->value.IsBool()) m_is_default = is_default->value.GetBool(); - auto next_unique_name = json.FindMember("nextUniqueName"); - if (next_unique_name != json.MemberBegin() && next_unique_name->value.IsInt()) - m_next_unique_name = next_unique_name->value.GetInt(); + auto next_id = json.FindMember("nextId"); + if (next_id != json.MemberBegin() && next_id->value.IsUint64()) + m_next_id = next_id->value.GetUint64(); auto base_layout = json.FindMember("baseLayout"); if (base_layout != json.MemberEnd() && base_layout->value.IsString()) @@ -766,6 +768,10 @@ void DockLayout::load( if (unique_name == object.MemberEnd() || !unique_name->value.IsString()) continue; + auto id = object.FindMember("id"); + if (id == object.MemberEnd() || !id->value.IsUint64()) + continue; + auto widgets_iterator = m_widgets.find(unique_name->value.GetString()); if (widgets_iterator != m_widgets.end()) continue; @@ -790,6 +796,7 @@ void DockLayout::load( DebuggerWidgetParameters parameters; parameters.unique_name = unique_name->value.GetString(); + parameters.id = id->value.GetUint64(); parameters.cpu = &DebugInterface::get(m_cpu); parameters.cpu_override = cpu_override; @@ -900,19 +907,20 @@ void DockLayout::setupDefaultLayout() group->setCurrentTabIndex(0); } -QString DockLayout::generateNewUniqueName(const char* type) +std::pair DockLayout::generateNewUniqueName(const char* type) { QString name; + u64 id; + do { - if (m_next_unique_name == INT_MAX) - return QString(); + if (m_next_id == INT_MAX) + return {QString(), 0}; - // Produce unique names that will lexicographically sort in the order - // they were allocated. This ensures the #1, #2, etc suffixes for dock - // widgets with conflicting names will be assigned in the correct order. - name = QStringLiteral("%1-%2").arg(m_next_unique_name, 16, 10, QLatin1Char('0')).arg(type); - m_next_unique_name++; + id = m_next_id; + name = QStringLiteral("%1-%2").arg(type).arg(static_cast(m_next_id)); + m_next_id++; } while (hasDebuggerWidget(name)); - return name; + + return {name, id}; } diff --git a/pcsx2-qt/Debugger/Docking/DockLayout.h b/pcsx2-qt/Debugger/Docking/DockLayout.h index 8fad50c6a7..3bf8b7ef69 100644 --- a/pcsx2-qt/Debugger/Docking/DockLayout.h +++ b/pcsx2-qt/Debugger/Docking/DockLayout.h @@ -120,7 +120,7 @@ private: void setupDefaultLayout(); - QString generateNewUniqueName(const char* type); + std::pair generateNewUniqueName(const char* type); // The name displayed in the user interface. Also used to determine the // file name for the layout file. @@ -134,7 +134,7 @@ private: bool m_is_default = false; // A counter used to generate new unique names for dock widgets. - int m_next_unique_name = 0; + u64 m_next_id = 0; // The name of the default layout which this layout was based on. This will // be used if the m_geometry variable above is empty. diff --git a/pcsx2-qt/Debugger/Docking/DockTables.cpp b/pcsx2-qt/Debugger/Docking/DockTables.cpp index d86a39c207..a326129bc2 100644 --- a/pcsx2-qt/Debugger/Docking/DockTables.cpp +++ b/pcsx2-qt/Debugger/Docking/DockTables.cpp @@ -14,12 +14,14 @@ #include "Debugger/Memory/SavedAddressesWidget.h" #include "Debugger/SymbolTree/SymbolTreeWidgets.h" -#include "common/MD5Digest.h" - -#include "fmt/format.h" - using namespace DockUtils; +static void hashDefaultLayout(const DockTables::DefaultDockLayout& layout, u32& hash); +static void hashDefaultGroup(const DockTables::DefaultDockGroupDescription& group, u32& hash); +static void hashDefaultDockWidget(const DockTables::DefaultDockWidgetDescription& widget, u32& hash); +static void hashNumber(u32 number, u32& hash); +static void hashString(const char* string, u32& hash); + #define DEBUGGER_WIDGET(type, display_name, preferred_location) \ { \ #type, \ @@ -123,67 +125,43 @@ const DockTables::DefaultDockLayout* DockTables::defaultLayout(const std::string return nullptr; } -const std::string& DockTables::hashDefaultLayouts() +u32 DockTables::hashDefaultLayouts() { - static std::string hash; - if (!hash.empty()) - return hash; + static std::optional hash; + if (hash.has_value()) + return *hash; - MD5Digest md5; + hash.emplace(0); - u32 hash_version = 1; - md5.Update(&hash_version, sizeof(hash_version)); - - u32 layout_count = static_cast(DEFAULT_DOCK_LAYOUTS.size()); - md5.Update(&layout_count, sizeof(layout_count)); + u32 hash_version = 2; + hashNumber(hash_version, *hash); + hashNumber(static_cast(DEFAULT_DOCK_LAYOUTS.size()), *hash); for (const DefaultDockLayout& layout : DEFAULT_DOCK_LAYOUTS) - hashDefaultLayout(layout, md5); + hashDefaultLayout(layout, *hash); - u8 digest[16]; - md5.Final(digest); - hash = fmt::format( - "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", - digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7], - digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]); - - return hash; + return *hash; } -void DockTables::hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5) +static void hashDefaultLayout(const DockTables::DefaultDockLayout& layout, u32& hash) { - u32 layout_name_size = static_cast(layout.name.size()); - md5.Update(&layout_name_size, sizeof(layout_name_size)); - md5.Update(layout.name.data(), layout_name_size); + hashString(layout.name.c_str(), hash); + hashString(DebugInterface::cpuName(layout.cpu), hash); - const char* cpu_name = DebugInterface::cpuName(layout.cpu); - u32 cpu_name_size = static_cast(strlen(cpu_name)); - md5.Update(&cpu_name_size, sizeof(cpu_name_size)); - md5.Update(cpu_name, cpu_name_size); + hashNumber(static_cast(layout.groups.size()), hash); + for (const DockTables::DefaultDockGroupDescription& group : layout.groups) + hashDefaultGroup(group, hash); - u32 group_count = static_cast(layout.groups.size()); - md5.Update(&group_count, sizeof(group_count)); + hashNumber(static_cast(layout.widgets.size()), hash); + for (const DockTables::DefaultDockWidgetDescription& widget : layout.widgets) + hashDefaultDockWidget(widget, hash); - for (const DefaultDockGroupDescription& group : layout.groups) - hashDefaultGroup(group, md5); - - u32 widget_count = static_cast(layout.widgets.size()); - md5.Update(&widget_count, sizeof(widget_count)); - - for (const DefaultDockWidgetDescription& widget : layout.widgets) - hashDefaultDockWidget(widget, md5); - - u32 toolbar_count = static_cast(layout.toolbars.size()); - md5.Update(&toolbar_count, sizeof(toolbar_count)); + hashNumber(static_cast(layout.toolbars.size()), hash); for (const std::string& toolbar : layout.toolbars) - { - u32 toolbar_size = toolbar.size(); - md5.Update(&toolbar_size, sizeof(toolbar_size)); - md5.Update(toolbar.data(), toolbar.size()); - } + hashString(toolbar.c_str(), hash); } -void DockTables::hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5) +static void hashDefaultGroup(const DockTables::DefaultDockGroupDescription& group, u32& hash) { // This is inline here so that it's obvious that changing it will affect the // result of the hash. @@ -207,20 +185,25 @@ void DockTables::hashDefaultGroup(const DefaultDockGroupDescription& group, MD5D break; } - u32 location_size = static_cast(strlen(location)); - md5.Update(&location_size, sizeof(location_size)); - md5.Update(location, location_size); - - u32 parent = static_cast(group.parent); - md5.Update(&parent, sizeof(parent)); + hashString(location, hash); + hashNumber(static_cast(group.parent), hash); } -void DockTables::hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5) +static void hashDefaultDockWidget(const DockTables::DefaultDockWidgetDescription& widget, u32& hash) { - u32 type_size = static_cast(widget.type.size()); - md5.Update(&type_size, sizeof(type_size)); - md5.Update(widget.type.data(), type_size); - - u32 group = static_cast(widget.group); - md5.Update(&group, sizeof(group)); + hashString(widget.type.c_str(), hash); + hashNumber(static_cast(widget.group), hash); +} + +static void hashNumber(u32 number, u32& hash) +{ + hash = hash * 31 + number; +} + +static void hashString(const char* string, u32& hash) +{ + u32 size = static_cast(strlen(string)); + hash = hash * 31 + size; + for (u32 i = 0; i < size; i++) + hash = hash * 31 + string[i]; } diff --git a/pcsx2-qt/Debugger/Docking/DockTables.h b/pcsx2-qt/Debugger/Docking/DockTables.h index d332aa988f..df99a677b4 100644 --- a/pcsx2-qt/Debugger/Docking/DockTables.h +++ b/pcsx2-qt/Debugger/Docking/DockTables.h @@ -67,9 +67,5 @@ namespace DockTables // This is used to determine if the user has updated and we need to recreate // the default layouts. - const std::string& hashDefaultLayouts(); - - void hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5); - void hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5); - void hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5); + u32 hashDefaultLayouts(); } // namespace DockTables