From 1057020d4fdc688892ac5e0ef26ddf71c889798a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 5 Nov 2022 12:27:56 -0700 Subject: [PATCH] render: separate off project copying functionality from PreviewAutoCacher --- app/render/CMakeLists.txt | 2 + app/render/previewautocacher.cpp | 268 ++++--------------------------- app/render/previewautocacher.h | 65 +------- app/render/projectcopier.cpp | 259 +++++++++++++++++++++++++++++ app/render/projectcopier.h | 125 ++++++++++++++ 5 files changed, 419 insertions(+), 300 deletions(-) create mode 100644 app/render/projectcopier.cpp create mode 100644 app/render/projectcopier.h diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 068552763..e2e310fcc 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -47,6 +47,8 @@ set(OLIVE_SOURCES render/previewaudiodevice.h render/previewautocacher.cpp render/previewautocacher.h + render/projectcopier.cpp + render/projectcopier.h render/renderer.cpp render/renderer.h render/rendercache.h diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 08624be67..44112b55c 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -28,11 +28,6 @@ #include "node/inputdragger.h" #include "node/project/project.h" #include "render/diskmanager.h" -#include "render/renderprocessor.h" -#include "task/customcache/customcachetask.h" -#include "task/taskmanager.h" -#include "widget/slider/base/numericsliderbase.h" -#include "widget/viewer/viewer.h" namespace olive { @@ -47,6 +42,10 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : multicam_(nullptr), ignore_cache_requests_(false) { + copier_ = new ProjectCopier(this); + connect(copier_, &ProjectCopier::AddedNode, this, &PreviewAutoCacher::ConnectToNodeCache); + connect(copier_, &ProjectCopier::RemovedNode, this, &PreviewAutoCacher::DisconnectFromNodeCache); + // Set defaults SetPlayhead(0); @@ -157,7 +156,7 @@ void PreviewAutoCacher::AudioRendered() if (running_audio_tasks_.removeOne(watcher)) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket TimeRange range = watcher->property("time").value(); - Node *node = copy_map_.key(Node::ValueToPtr(watcher->property("node"))); + Node *node = copier_->GetOriginal(Node::ValueToPtr(watcher->property("node"))); if (watcher->HasResult() && node) { if (PlaybackCache *cache = Node::ValueToPtr(watcher->property("cache"))) { @@ -252,140 +251,12 @@ void PreviewAutoCacher::VideoRendered() delete watcher; } -void PreviewAutoCacher::ProcessUpdateQueue() -{ - // Iterate everything that happened to the graph and do the same thing on our end - while (!graph_update_queue_.empty()) { - QueuedJob job = graph_update_queue_.front(); - graph_update_queue_.pop_front(); - - switch (job.type) { - case QueuedJob::kNodeAdded: - AddNode(job.node); - break; - case QueuedJob::kNodeRemoved: - RemoveNode(job.node); - break; - case QueuedJob::kEdgeAdded: - AddEdge(job.output, job.input); - break; - case QueuedJob::kEdgeRemoved: - RemoveEdge(job.output, job.input); - break; - case QueuedJob::kValueChanged: - CopyValue(job.input); - break; - case QueuedJob::kValueHintChanged: - CopyValueHint(job.input); - break; - } - } - - // Indicate that we have synchronized to this point, which is compared with the graph change - // time to see if our copied graph is up to date - UpdateLastSyncedValue(); -} - -void PreviewAutoCacher::AddNode(Node *node) -{ - if (dynamic_cast(node)) { - // Group nodes are just dummy nodes, no need to copy them - return; - } - - // Copy node - Node* copy = node->copy(); - - // Add to project - copy->setParent(&copied_project_); - - // Disable caches for copy - copy->SetCachesEnabled(false); - - // Copy cache UUIDs - copy->CopyCacheUuidsFrom(node); - - // Insert into map - InsertIntoCopyMap(node, copy); - - // Keep track of our nodes - created_nodes_.append(copy); -} - -void PreviewAutoCacher::RemoveNode(Node *node) -{ - // Find our copy and remove it - Node* copy = copy_map_.take(node); - - // Disconnect from node's caches - DisconnectFromNodeCache(node); - - // Remove from created list - created_nodes_.removeOne(copy); - - // Delete it - delete copy; -} - -void PreviewAutoCacher::AddEdge(Node *output, const NodeInput &input) -{ - // Create same connection with our copied graph - Node* our_output = copy_map_.value(output); - Node* our_input = copy_map_.value(input.node()); - - Node::ConnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); -} - -void PreviewAutoCacher::RemoveEdge(Node *output, const NodeInput &input) -{ - // Remove same connection with our copied graph - Node* our_output = copy_map_.value(output); - Node* our_input = copy_map_.value(input.node()); - - Node::DisconnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); -} - -void PreviewAutoCacher::CopyValue(const NodeInput &input) -{ - if (dynamic_cast(input.node())) { - // Group nodes are just dummy nodes, no need to copy them - return; - } - - // Copy all values to our graph - Node* our_input = copy_map_.value(input.node()); - Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element()); -} - -void PreviewAutoCacher::CopyValueHint(const NodeInput &input) -{ - if (dynamic_cast(input.node())) { - // Group nodes are just dummy nodes, no need to copy them - return; - } - - // Copy value hint to our graph - Node* our_input = copy_map_.value(input.node()); - Node::ValueHint hint = input.node()->GetValueHintForInput(input.input(), input.element()); - our_input->SetValueHintForInput(input.input(), hint, input.element()); -} - -void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) -{ - // Insert into map - copy_map_.insert(node, copy); - - // Copy parameters - Node::CopyInputs(node, copy, false); - - // Connect to node's cache - if (!ignore_cache_requests_) { - ConnectToNodeCache(node); - } -} - void PreviewAutoCacher::ConnectToNodeCache(Node *node) { + if (!ignore_cache_requests_) { + return; + } + connect(node->video_frame_cache(), &PlaybackCache::Requested, this, @@ -455,16 +326,6 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) &PreviewAutoCacher::CancelForCache); } -void PreviewAutoCacher::UpdateGraphChangeValue() -{ - graph_changed_time_.Acquire(); -} - -void PreviewAutoCacher::UpdateLastSyncedValue() -{ - last_update_time_.Acquire(); -} - void PreviewAutoCacher::CancelQueuedSingleFrameRender() { if (single_frame_render_) { @@ -477,7 +338,7 @@ void PreviewAutoCacher::CancelQueuedSingleFrameRender() void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker) { range_list->insert(range); - tracker->insert(range, graph_changed_time_); + tracker->insert(range, copier_->GetGraphChangeTime()); } void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeRange &range) @@ -494,7 +355,7 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR TimeRangeListFrameIterator iterator({range}, using_tb); pending_video_jobs_.push_back({node, cache, range, iterator}); - video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_); + video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), copier_->GetGraphChangeTime()); TryRender(); } @@ -505,7 +366,7 @@ void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeR cache->ClearRequestRange(range); pending_audio_jobs_.push_back({node, cache, range}); - audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_); + audio_cache_data_[cache].job_tracker.insert(range, copier_->GetGraphChangeTime()); TryRender(); } @@ -592,47 +453,11 @@ void PreviewAutoCacher::SetThumbnailsPaused(bool e) } } -void PreviewAutoCacher::NodeAdded(Node *node) -{ - graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::NodeRemoved(Node *node) -{ - graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input) -{ - graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input) -{ - graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::ValueChanged(const NodeInput &input) -{ - graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::ValueHintChanged(const NodeInput &input) -{ - graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); - UpdateGraphChangeValue(); -} - void PreviewAutoCacher::TryRender() { delayed_requeue_timer_.stop(); - if (!graph_update_queue_.empty()) { + if (copier_->HasUpdatesInQueue()) { // Check if we have jobs running in other threads that shouldn't be interrupted right now // NOTE: We don't check for downloads because, while they run in another thread, they don't // require any access to the graph and therefore don't risk race conditions. @@ -642,7 +467,7 @@ void PreviewAutoCacher::TryRender() } // No jobs are active, we can process the update queue - ProcessUpdateQueue(); + copier_->ProcessUpdateQueue(); } if (single_frame_render_) { @@ -653,7 +478,7 @@ void PreviewAutoCacher::TryRender() // Check if already caching this Node *n = Node::ValueToPtr(t->property("node")); - Node *copy = copy_map_.value(n); + Node *copy = copier_->GetCopy(n); if (copy) { RenderTicketWatcher *watcher = RenderFrame(copy, @@ -676,7 +501,7 @@ void PreviewAutoCacher::TryRender() while (!pending_video_jobs_.empty()) { VideoJob &d = pending_video_jobs_.front(); - if (Node *copy = copy_map_.value(d.node)) { + if (Node *copy = copier_->GetCopy(d.node)) { // Queue next frames rational t; while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { @@ -707,7 +532,7 @@ void PreviewAutoCacher::TryRender() bool pop = true; // Start job - if (Node *copy = copy_map_.value(d.node)) { + if (Node *copy = copier_->GetCopy(d.node)) { TimeRange &queued_range = d.range; TimeRange use_range = queued_range; @@ -736,7 +561,7 @@ void PreviewAutoCacher::TryRender() RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, PlaybackCache *cache, bool dry) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); - watcher->setProperty("job", QVariant::fromValue(last_update_time_)); + watcher->setProperty("job", QVariant::fromValue(copier_->GetLastUpdateTime())); watcher->setProperty("cache", Node::PtrToValue(cache)); watcher->setProperty("time", QVariant::fromValue(time)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); @@ -768,7 +593,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& rvp.use_cache = true; // Multicam - rvp.multicam = static_cast(copy_map_.value(multicam_)); + rvp.multicam = copier_->GetCopy(multicam_); watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp)); @@ -778,7 +603,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, PlaybackCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); - watcher->setProperty("job", QVariant::fromValue(last_update_time_)); + watcher->setProperty("job", QVariant::fromValue(copier_->GetLastUpdateTime())); watcher->setProperty("node", Node::PtrToValue(node)); watcher->setProperty("cache", Node::PtrToValue(cache)); watcher->setProperty("time", QVariant::fromValue(r)); @@ -861,16 +686,13 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) video_immediate_passthroughs_.clear(); // Disconnect from all node cache's - for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { + for (auto it=copier_->GetNodeMap().cbegin(); it!=copier_->GetNodeMap().cend(); it++) { DisconnectFromNodeCache(it.key()); } // Delete all of our copied nodes - qDeleteAll(created_nodes_); - created_nodes_.clear(); - copy_map_.clear(); + copier_->SetProject(nullptr); copied_viewer_node_ = nullptr; - graph_update_queue_.clear(); // Ensure all cache data is cleared video_cache_data_.clear(); @@ -878,59 +700,25 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) // Clear multicam reference multicam_ = nullptr; - - // Disconnect signals for future node additions/deletions - NodeGraph* graph = viewer_node_->parent(); - - disconnect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded); - disconnect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved); - disconnect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded); - disconnect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved); - disconnect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged); - disconnect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged); } viewer_node_ = viewer_node; if (viewer_node_) { - // Copy graph - NodeGraph* graph = viewer_node_->parent(); + // Copy graph (this should always be a Project) + Project* graph = static_cast(viewer_node_->parent()); SetRendersPaused(true); - // Add all nodes - for (int i=0; inodes().at(i), copied_project_.nodes().at(i)); - } - for (int i=copied_project_.nodes().size(); inodes().size(); i++) { - AddNode(graph->nodes().at(i)); - } + copier_->SetProject(graph); + for (int i=0; inodes().size(); i++) { graph->nodes().at(i)->ConnectedToPreviewEvent(); } // Find copied viewer node - copied_viewer_node_ = static_cast(copy_map_.value(viewer_node_)); - copied_color_manager_ = static_cast(copy_map_.value(viewer_node_->project()->color_manager())); - - // Add all connections - foreach (Node* node, graph->nodes()) { - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - AddEdge(it->second, it->first); - } - } - - // Ensure graph change value is just before the sync value - UpdateGraphChangeValue(); - UpdateLastSyncedValue(); - - // Connect signals for future node additions/deletions - connect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded, Qt::DirectConnection); - connect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved, Qt::DirectConnection); - connect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded, Qt::DirectConnection); - connect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved, Qt::DirectConnection); - connect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged, Qt::DirectConnection); - connect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged, Qt::DirectConnection); + copied_viewer_node_ = copier_->GetCopy(viewer_node_); + copied_color_manager_ = copier_->GetCopy(graph->color_manager()); SetRendersPaused(false); } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 21efb9912..4aabca518 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -31,6 +31,7 @@ #include "node/output/viewer/viewer.h" #include "node/project/project.h" #include "render/audioparams.h" +#include "render/projectcopier.h" #include "render/renderjobtracker.h" #include "render/rendermanager.h" @@ -113,29 +114,9 @@ private: RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, PlaybackCache *cache); - /** - * @brief Process all changes to internal NodeGraph copy - * - * PreviewAutoCacher staggers updates to its internal NodeGraph copy, only applying them when the - * RenderManager is not reading from it. This function is called when such an opportunity arises. - */ - void ProcessUpdateQueue(); - - void AddNode(Node* node); - void RemoveNode(Node* node); - void AddEdge(Node *output, const NodeInput& input); - void RemoveEdge(Node *output, const NodeInput& input); - void CopyValue(const NodeInput& input); - void CopyValueHint(const NodeInput& input); - - void InsertIntoCopyMap(Node* node, Node* copy); - void ConnectToNodeCache(Node *node); void DisconnectFromNodeCache(Node *node); - void UpdateGraphChangeValue(); - void UpdateLastSyncedValue(); - void CancelQueuedSingleFrameRender(); void StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker); @@ -145,33 +126,9 @@ private: void VideoInvalidatedFromNode(PlaybackCache *cache, const olive::TimeRange &range); void AudioInvalidatedFromNode(PlaybackCache *cache, const olive::TimeRange &range); - class QueuedJob { - public: - enum Type { - kNodeAdded, - kNodeRemoved, - kEdgeAdded, - kEdgeRemoved, - kValueChanged, - kValueHintChanged - }; - - Type type; - Node* node; - NodeInput input; - Node *output; - }; - ViewerOutput* viewer_node_; - Project copied_project_; - - std::list graph_update_queue_; - QHash copy_map_; - QHash graph_map_; - ViewerOutput* copied_viewer_node_; - ColorManager* copied_color_manager_; - QVector created_nodes_; + ProjectCopier *copier_; TimeRange cache_range_; @@ -184,9 +141,6 @@ private: RenderTicketPtr single_frame_render_; QMap > video_immediate_passthroughs_; - JobTime graph_changed_time_; - JobTime last_update_time_; - QTimer delayed_requeue_timer_; JobTime last_conform_task_; @@ -194,6 +148,9 @@ private: QVector running_video_tasks_; QVector running_audio_tasks_; + ViewerOutput* copied_viewer_node_; + ColorManager* copied_color_manager_; + struct VideoJob { Node *node; PlaybackCache *cache; @@ -251,18 +208,6 @@ private slots: */ void VideoRendered(); - void NodeAdded(Node* node); - - void NodeRemoved(Node* node); - - void EdgeAdded(Node *output, const NodeInput& input); - - void EdgeRemoved(Node *output, const NodeInput& input); - - void ValueChanged(const NodeInput& input); - - void ValueHintChanged(const NodeInput &input); - /** * @brief Generic function called whenever the frames to render need to be (re)queued */ diff --git a/app/render/projectcopier.cpp b/app/render/projectcopier.cpp new file mode 100644 index 000000000..89ff9f1e4 --- /dev/null +++ b/app/render/projectcopier.cpp @@ -0,0 +1,259 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "projectcopier.h" + +namespace olive { + +ProjectCopier::ProjectCopier(QObject *parent) : + QObject(parent) +{ + original_ = nullptr; + copy_ = new Project(); + copy_->setParent(this); +} + +void ProjectCopier::SetProject(Project *project) +{ + if (original_) { + // Clear current project + qDeleteAll(created_nodes_); + created_nodes_.clear(); + copy_map_.clear(); + graph_update_queue_.clear(); + + disconnect(original_, &NodeGraph::NodeAdded, this, &ProjectCopier::QueueNodeAdd); + disconnect(original_, &NodeGraph::NodeRemoved, this, &ProjectCopier::QueueNodeRemove); + disconnect(original_, &NodeGraph::InputConnected, this, &ProjectCopier::QueueEdgeAdd); + disconnect(original_, &NodeGraph::InputDisconnected, this, &ProjectCopier::QueueEdgeRemove); + disconnect(original_, &NodeGraph::ValueChanged, this, &ProjectCopier::QueueValueChange); + disconnect(original_, &NodeGraph::InputValueHintChanged, this, &ProjectCopier::QueueValueHintChange); + } + + original_ = project; + + if (original_) { + // Add all nodes + for (int i=0; inodes().size(); i++) { + InsertIntoCopyMap(original_->nodes().at(i), copy_->nodes().at(i)); + } + + for (int i=copy_->nodes().size(); inodes().size(); i++) { + DoNodeAdd(original_->nodes().at(i)); + } + + // Add all connections + foreach (Node* node, original_->nodes()) { + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + DoEdgeAdd(it->second, it->first); + } + } + + // Ensure graph change value is just before the sync value + UpdateGraphChangeValue(); + UpdateLastSyncedValue(); + + // Connect signals for future node additions/deletions + connect(original_, &NodeGraph::NodeAdded, this, &ProjectCopier::QueueNodeAdd, Qt::DirectConnection); + connect(original_, &NodeGraph::NodeRemoved, this, &ProjectCopier::QueueNodeRemove, Qt::DirectConnection); + connect(original_, &NodeGraph::InputConnected, this, &ProjectCopier::QueueEdgeAdd, Qt::DirectConnection); + connect(original_, &NodeGraph::InputDisconnected, this, &ProjectCopier::QueueEdgeRemove, Qt::DirectConnection); + connect(original_, &NodeGraph::ValueChanged, this, &ProjectCopier::QueueValueChange, Qt::DirectConnection); + connect(original_, &NodeGraph::InputValueHintChanged, this, &ProjectCopier::QueueValueHintChange, Qt::DirectConnection); + } +} + +void ProjectCopier::ProcessUpdateQueue() +{ + // Iterate everything that happened to the graph and do the same thing on our end + while (!graph_update_queue_.empty()) { + QueuedJob job = graph_update_queue_.front(); + graph_update_queue_.pop_front(); + + switch (job.type) { + case QueuedJob::kNodeAdded: + DoNodeAdd(job.node); + break; + case QueuedJob::kNodeRemoved: + DoNodeRemove(job.node); + break; + case QueuedJob::kEdgeAdded: + DoEdgeAdd(job.output, job.input); + break; + case QueuedJob::kEdgeRemoved: + DoEdgeRemove(job.output, job.input); + break; + case QueuedJob::kValueChanged: + DoValueChange(job.input); + break; + case QueuedJob::kValueHintChanged: + DoValueHintChange(job.input); + break; + } + } + + // Indicate that we have synchronized to this point, which is compared with the graph change + // time to see if our copied graph is up to date + UpdateLastSyncedValue(); +} + +void ProjectCopier::DoNodeAdd(Node *node) +{ + if (dynamic_cast(node)) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + + // Copy node + Node* copy = node->copy(); + + // Add to project + copy->setParent(copy_); + + // Disable caches for copy + copy->SetCachesEnabled(false); + + // Copy cache UUIDs + copy->CopyCacheUuidsFrom(node); + + // Insert into map + InsertIntoCopyMap(node, copy); + + // Keep track of our nodes + created_nodes_.append(copy); +} + +void ProjectCopier::DoNodeRemove(Node *node) +{ + // Find our copy and remove it + Node* copy = copy_map_.take(node); + + // Disconnect from node's caches + emit RemovedNode(node); + + // Remove from created list + created_nodes_.removeOne(copy); + + // Delete it + delete copy; +} + +void ProjectCopier::DoEdgeAdd(Node *output, const NodeInput &input) +{ + // Create same connection with our copied graph + Node* our_output = copy_map_.value(output); + Node* our_input = copy_map_.value(input.node()); + + Node::ConnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); +} + +void ProjectCopier::DoEdgeRemove(Node *output, const NodeInput &input) +{ + // Remove same connection with our copied graph + Node* our_output = copy_map_.value(output); + Node* our_input = copy_map_.value(input.node()); + + Node::DisconnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); +} + +void ProjectCopier::DoValueChange(const NodeInput &input) +{ + if (dynamic_cast(input.node())) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + + // Copy all values to our graph + Node* our_input = copy_map_.value(input.node()); + Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element()); +} + +void ProjectCopier::DoValueHintChange(const NodeInput &input) +{ + if (dynamic_cast(input.node())) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + + // Copy value hint to our graph + Node* our_input = copy_map_.value(input.node()); + Node::ValueHint hint = input.node()->GetValueHintForInput(input.input(), input.element()); + our_input->SetValueHintForInput(input.input(), hint, input.element()); +} + +void ProjectCopier::InsertIntoCopyMap(Node *node, Node *copy) +{ + // Insert into map + copy_map_.insert(node, copy); + + // Copy parameters + Node::CopyInputs(node, copy, false); + + // Connect to node's cache + emit AddedNode(node); +} + +void ProjectCopier::QueueNodeAdd(Node *node) +{ + graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueNodeRemove(Node *node) +{ + graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueEdgeAdd(Node *output, const NodeInput &input) +{ + graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueEdgeRemove(Node *output, const NodeInput &input) +{ + graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueValueChange(const NodeInput &input) +{ + graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueValueHintChange(const NodeInput &input) +{ + graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::UpdateGraphChangeValue() +{ + graph_changed_time_.Acquire(); +} + +void ProjectCopier::UpdateLastSyncedValue() +{ + last_update_time_.Acquire(); +} + +} diff --git a/app/render/projectcopier.h b/app/render/projectcopier.h new file mode 100644 index 000000000..42fd14daa --- /dev/null +++ b/app/render/projectcopier.h @@ -0,0 +1,125 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef PROJECTCOPIER_H +#define PROJECTCOPIER_H + +#include "node/project/project.h" + +namespace olive { + +class ProjectCopier : public QObject +{ + Q_OBJECT +public: + ProjectCopier(QObject *parent = nullptr); + + void SetProject(Project *project); + + template + T *GetCopy(T *original) + { + return static_cast(copy_map_.value(original)); + } + + template + T *GetOriginal(T *copy) + { + return static_cast(copy_map_.key(copy)); + } + + const QHash &GetNodeMap() const { return copy_map_; } + + const JobTime &GetGraphChangeTime() const { return graph_changed_time_; } + const JobTime &GetLastUpdateTime() const { return last_update_time_; } + + bool HasUpdatesInQueue() const { return !graph_update_queue_.empty(); } + + /** + * @brief Process all changes to internal NodeGraph copy + * + * PreviewAutoCacher staggers updates to its internal NodeGraph copy, only applying them when the + * RenderManager is not reading from it. This function is called when such an opportunity arises. + */ + void ProcessUpdateQueue(); + +signals: + void AddedNode(Node *n); + void RemovedNode(Node *n); + +private: + void DoNodeAdd(Node* node); + void DoNodeRemove(Node* node); + void DoEdgeAdd(Node *output, const NodeInput& input); + void DoEdgeRemove(Node *output, const NodeInput& input); + void DoValueChange(const NodeInput& input); + void DoValueHintChange(const NodeInput& input); + + void InsertIntoCopyMap(Node* node, Node* copy); + + void UpdateGraphChangeValue(); + void UpdateLastSyncedValue(); + + Project *original_; + Project *copy_; + + class QueuedJob { + public: + enum Type { + kNodeAdded, + kNodeRemoved, + kEdgeAdded, + kEdgeRemoved, + kValueChanged, + kValueHintChanged + }; + + Type type; + Node* node; + NodeInput input; + Node *output; + }; + + std::list graph_update_queue_; + QHash copy_map_; + QHash graph_map_; + QVector created_nodes_; + + JobTime graph_changed_time_; + JobTime last_update_time_; + +private slots: + void QueueNodeAdd(Node* node); + + void QueueNodeRemove(Node* node); + + void QueueEdgeAdd(Node *output, const NodeInput& input); + + void QueueEdgeRemove(Node *output, const NodeInput& input); + + void QueueValueChange(const NodeInput& input); + + void QueueValueHintChange(const NodeInput &input); + +}; + +} + +#endif // PROJECTCOPIER_H