timeline: avoid duplicate items or item dependencies when pasting

God damn this was a big oof. Pasting was inadvertently making duplicates of the footage node(s) every single time. Worst case was if the clip was connected to a nested sequence. Every time someone copy/pasted in my large-scale project, it would add another 1000 nodes to the project. It's about fucking time this was fixed.
This commit is contained in:
itsmattkc
2023-02-28 22:28:32 -08:00
parent 43e4e78378
commit 066a10e6a0
5 changed files with 108 additions and 13 deletions
+2 -2
View File
@@ -136,7 +136,7 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamRe
return res;
}
ProjectSerializer::Result ProjectSerializer::Paste(LoadType load_type)
ProjectSerializer::Result ProjectSerializer::Paste(LoadType load_type, Project *project)
{
QString clipboard = Core::PasteStringFromClipboard();
if (clipboard.isEmpty()) {
@@ -145,7 +145,7 @@ ProjectSerializer::Result ProjectSerializer::Paste(LoadType load_type)
QXmlStreamReader reader(clipboard);
return ProjectSerializer::Load(nullptr, &reader, load_type);
return ProjectSerializer::Load(project, &reader, load_type);
}
ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data, bool compress)
+3 -1
View File
@@ -81,6 +81,8 @@ public:
QVector<Node*> nodes;
Node::OutputConnections promised_connections;
};
class Result
@@ -171,7 +173,7 @@ public:
static Result Load(Project *project, const QString &filename, LoadType load_type);
static Result Load(Project *project, QXmlStreamReader *read_device, LoadType load_type);
static Result Paste(LoadType load_type);
static Result Paste(LoadType load_type, Project *project = nullptr);
static Result Save(const SaveData &data, bool compress);
static Result Save(QXmlStreamWriter *write_device, const SaveData &data);
@@ -175,13 +175,25 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project
case kOnlyNodes:
{
if (reader->name() == QStringLiteral("nodes")) {
QMap<quintptr, Node*> skipped_items;
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("node")) {
QString id;
quintptr ptr = 0;
QVector<quintptr> items;
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
id = attr.value().toString();
} else if (attr.name() == QStringLiteral("ptr")) {
ptr = attr.value().toULongLong();
} else if (attr.name() == QStringLiteral("items")) {
QVector<QStringRef> l = attr.value().split(',');
items.reserve(l.size());
for (const QStringRef &s : l) {
items.append(s.toULongLong());
}
}
}
@@ -189,15 +201,49 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project
qWarning() << "Failed to load node with empty ID";
reader->skipCurrentElement();
} else {
Node* node = NodeFactory::CreateFromID(id);
if (!node) {
qWarning() << "Failed to find node with ID" << id;
bool dependency_of_item = false;
if (project && !items.empty()) {
for (quintptr p : items) {
if (project->nodes().contains(reinterpret_cast<Node*>(p))) {
dependency_of_item = true;
break;
}
}
}
if (dependency_of_item) {
reader->skipCurrentElement();
} else {
// Disable cache while node is being loaded (we'll re-enable it later)
node->SetCachesEnabled(false);
node->Load(reader, &project_data);
load_data.nodes.append(node);
Node* node = NodeFactory::CreateFromID(id);
if (!node) {
qWarning() << "Failed to find node with ID" << id;
reader->skipCurrentElement();
} else {
if (project && node->IsItem() && ptr) {
// If we're pasting an object into the same project, we should re-use the item
// rather than duplicate.
Node *existing = reinterpret_cast<Node *>(ptr);
if (project->nodes().contains(existing)) {
// Connect this
skipped_items.insert(ptr, existing);
// Don't continue loading this
delete node;
node = nullptr;
// Skip element
reader->skipCurrentElement();
}
}
if (node) {
// Disable cache while node is being loaded (we'll re-enable it later)
node->SetCachesEnabled(false);
node->Load(reader, &project_data);
load_data.nodes.append(node);
}
}
}
}
} else if (reader->name() == QStringLiteral("properties")) {
@@ -230,6 +276,21 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project
}
}
if (!skipped_items.empty()) {
for (auto it = project_data.desired_connections.begin(); it != project_data.desired_connections.end(); ) {
const SerializedData::SerializedConnection &sc = *it;
if (Node *si = skipped_items.value(sc.output_node)) {
// Convert this to a promised connection
Node::OutputConnection oc = {si, sc.input};
load_data.promised_connections.push_back(oc);
it = project_data.desired_connections.erase(it);
} else {
it++;
}
}
}
PostConnect(load_data.nodes, &project_data);
// Resolve serialized properties (if any)
@@ -249,6 +310,21 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project
return load_data;
}
void WriteNodeMap(QXmlStreamWriter *writer, Node *node, const QVector<Node*> &nodes)
{
writer->writeStartElement(QStringLiteral("node"));
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(node)));
for (auto oc : node->output_connections()) {
if (nodes.contains(oc.second.node())) {
WriteNodeMap(writer, oc.second.node(), nodes);
}
}
writer->writeEndElement();
}
void ProjectSerializer230220::Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const
{
if (!data.GetOnlySerializeMarkers().empty()) {
@@ -325,8 +401,19 @@ void ProjectSerializer230220::Save(QXmlStreamWriter *writer, const SaveData &dat
for (Node *n : data.GetOnlySerializeNodes()) {
writer->writeStartElement(QStringLiteral("node"));
QStringList item_list;
for (Node *i : data.GetOnlySerializeNodes()) {
if (i->IsItem() && i->InputsFrom(n, true)) {
item_list.append(QString::number(reinterpret_cast<quintptr>(i)));
}
}
if (!item_list.empty()) {
writer->writeAttribute(QStringLiteral("items"), item_list.join(','));
}
n->Save(writer);
writer->writeEndElement();
writer->writeEndElement(); // node
}
if (!data.GetProperties().empty()) {
-1
View File
@@ -60,7 +60,6 @@ struct SerializedData {
QList<BlockLink> block_links;
QVector<GroupLink> group_input_links;
QHash<NodeGroup*, quintptr> group_output_links;
QHash<Node*, QUuid> node_uuids;
};
+8 -1
View File
@@ -1938,7 +1938,7 @@ bool TimelineWidget::PasteInternal(bool insert)
return false;
}
ProjectSerializer::Result res = ProjectSerializer::Paste(ProjectSerializer::kOnlyNodes);
ProjectSerializer::Result res = ProjectSerializer::Paste(ProjectSerializer::kOnlyNodes, GetConnectedNode()->project());
if (res.GetLoadData().nodes.isEmpty()) {
return false;
}
@@ -1953,6 +1953,13 @@ bool TimelineWidget::PasteInternal(bool insert)
}
}
qDebug() << "pasing" << res.GetLoadData().nodes.size() << "nodes";
for (auto it = res.GetLoadData().promised_connections.cbegin(); it != res.GetLoadData().promised_connections.cend(); it++) {
auto oc = *it;
command->add_child(new NodeEdgeAddCommand(oc.first, oc.second));
}
rational paste_start = GetConnectedNode()->GetPlayhead();
if (insert) {