Debugger: Revise file format for UI layouts

This commit is contained in:
chaoticgd 2025-03-19 22:29:34 +00:00
parent c359c0e747
commit 76fd6e33ce
No known key found for this signature in database
GPG Key ID: FFFC3F38B3A0E6D8
6 changed files with 107 additions and 106 deletions

View File

@ -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();

View File

@ -16,6 +16,7 @@ class JsonValueWrapper;
struct DebuggerWidgetParameters
{
QString unique_name;
u64 id = 0;
DebugInterface* cpu = nullptr;
std::optional<BreakPointCpu> 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<const DebuggerEvents::Event*()> 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

View File

@ -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<int>(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<QString, u64> 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<qulonglong>(m_next_id));
m_next_id++;
} while (hasDebuggerWidget(name));
return name;
return {name, id};
}

View File

@ -120,7 +120,7 @@ private:
void setupDefaultLayout();
QString generateNewUniqueName(const char* type);
std::pair<QString, u64> 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.

View File

@ -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<u32> 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<u32>(DEFAULT_DOCK_LAYOUTS.size());
md5.Update(&layout_count, sizeof(layout_count));
u32 hash_version = 2;
hashNumber(hash_version, *hash);
hashNumber(static_cast<u32>(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<u32>(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<u32>(strlen(cpu_name));
md5.Update(&cpu_name_size, sizeof(cpu_name_size));
md5.Update(cpu_name, cpu_name_size);
hashNumber(static_cast<u32>(layout.groups.size()), hash);
for (const DockTables::DefaultDockGroupDescription& group : layout.groups)
hashDefaultGroup(group, hash);
u32 group_count = static_cast<u32>(layout.groups.size());
md5.Update(&group_count, sizeof(group_count));
hashNumber(static_cast<u32>(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<u32>(layout.widgets.size());
md5.Update(&widget_count, sizeof(widget_count));
for (const DefaultDockWidgetDescription& widget : layout.widgets)
hashDefaultDockWidget(widget, md5);
u32 toolbar_count = static_cast<u32>(layout.toolbars.size());
md5.Update(&toolbar_count, sizeof(toolbar_count));
hashNumber(static_cast<u32>(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<u32>(strlen(location));
md5.Update(&location_size, sizeof(location_size));
md5.Update(location, location_size);
u32 parent = static_cast<u32>(group.parent);
md5.Update(&parent, sizeof(parent));
hashString(location, hash);
hashNumber(static_cast<u32>(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<u32>(widget.type.size());
md5.Update(&type_size, sizeof(type_size));
md5.Update(widget.type.data(), type_size);
u32 group = static_cast<u32>(widget.group);
md5.Update(&group, sizeof(group));
hashString(widget.type.c_str(), hash);
hashNumber(static_cast<u32>(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<u32>(strlen(string));
hash = hash * 31 + size;
for (u32 i = 0; i < size; i++)
hash = hash * 31 + string[i];
}

View File

@ -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