build: split the engine into liboakengine.so; worker drops the UI entirely
Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).
- oak-render-worker now links liboakengine instead of the whole
libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
../app but backends now live in engine/; a stale pre-split liboakgl
in the build tree got dlopened instead, re-initialized and later
destroyed the interposed engine statics (full-suite segfault at
DialogSequenceParameterTab, found via gdb watchpoint)
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "projectcopier.h"
|
||||
|
||||
#include "node/group/group.h"
|
||||
#include "node/project/footage/footage.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ProjectCopier::ProjectCopier(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
original_ = nullptr;
|
||||
copy_ = new Project();
|
||||
copy_->setParent(this);
|
||||
}
|
||||
|
||||
void ProjectCopier::set_project(Project *project)
|
||||
{
|
||||
if (original_) {
|
||||
// Clear current project
|
||||
qDeleteAll(created_nodes_);
|
||||
created_nodes_.clear();
|
||||
copy_map_.clear();
|
||||
graph_update_queue_.clear();
|
||||
|
||||
disconnect(original_, &Project::node_added, this,
|
||||
&ProjectCopier::queue_node_add);
|
||||
disconnect(original_, &Project::node_removed, this,
|
||||
&ProjectCopier::queue_node_remove);
|
||||
disconnect(original_, &Project::input_connected, this,
|
||||
&ProjectCopier::queue_edge_add);
|
||||
disconnect(original_, &Project::input_disconnected, this,
|
||||
&ProjectCopier::queue_edge_remove);
|
||||
disconnect(original_, &Project::value_changed, this,
|
||||
&ProjectCopier::queue_value_change);
|
||||
disconnect(original_, &Project::input_value_hint_changed, this,
|
||||
&ProjectCopier::queue_value_hint_change);
|
||||
disconnect(original_, &Project::setting_changed, this,
|
||||
&ProjectCopier::queue_project_setting_change);
|
||||
}
|
||||
|
||||
original_ = project;
|
||||
|
||||
if (original_) {
|
||||
// The copied project is only used as an in-memory render proxy. Mark it so
|
||||
// downstream code (e.g. RenderWorkerPool) knows it is safe to reset its
|
||||
// modified flag after serializing a snapshot.
|
||||
copy_->setProperty("_oak_render_proxy", true);
|
||||
|
||||
// Add all nodes
|
||||
for (int i = 0; i < copy_->nodes().size(); i++) {
|
||||
insert_into_copy_map(original_->nodes().at(i), copy_->nodes().at(i));
|
||||
}
|
||||
|
||||
for (int i = copy_->nodes().size(); i < original_->nodes().size();
|
||||
i++) {
|
||||
do_node_add(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++) {
|
||||
do_edge_add(it->second, it->first);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy project settings
|
||||
Project::copy_settings(original_, copy_);
|
||||
|
||||
// Ensure graph change value is just before the sync value
|
||||
update_graph_change_value();
|
||||
update_last_synced_value();
|
||||
|
||||
// Connect signals for future node additions/deletions
|
||||
connect(original_, &Project::node_added, this,
|
||||
&ProjectCopier::queue_node_add, Qt::DirectConnection);
|
||||
connect(original_, &Project::node_removed, this,
|
||||
&ProjectCopier::queue_node_remove, Qt::DirectConnection);
|
||||
connect(original_, &Project::input_connected, this,
|
||||
&ProjectCopier::queue_edge_add, Qt::DirectConnection);
|
||||
connect(original_, &Project::input_disconnected, this,
|
||||
&ProjectCopier::queue_edge_remove, Qt::DirectConnection);
|
||||
connect(original_, &Project::value_changed, this,
|
||||
&ProjectCopier::queue_value_change, Qt::DirectConnection);
|
||||
connect(original_, &Project::input_value_hint_changed, this,
|
||||
&ProjectCopier::queue_value_hint_change, Qt::DirectConnection);
|
||||
connect(original_, &Project::setting_changed, this,
|
||||
&ProjectCopier::queue_project_setting_change,
|
||||
Qt::DirectConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectCopier::process_update_queue()
|
||||
{
|
||||
bool copy_changed = false;
|
||||
|
||||
// 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();
|
||||
copy_changed = true;
|
||||
|
||||
switch (job.type) {
|
||||
case QueuedJob::k_node_added:
|
||||
do_node_add(job.node);
|
||||
break;
|
||||
case QueuedJob::k_node_removed:
|
||||
do_node_remove(job.node);
|
||||
break;
|
||||
case QueuedJob::k_edge_added:
|
||||
do_edge_add(job.output, job.input);
|
||||
break;
|
||||
case QueuedJob::k_edge_removed:
|
||||
do_edge_remove(job.output, job.input);
|
||||
break;
|
||||
case QueuedJob::k_value_changed:
|
||||
do_value_change(job.input);
|
||||
break;
|
||||
case QueuedJob::k_value_hint_changed:
|
||||
do_value_hint_change(job.input);
|
||||
break;
|
||||
case QueuedJob::k_project_setting_changed:
|
||||
do_project_setting_change(job.key, job.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The copied project is not saved, so its modified flag is only used by the
|
||||
// render worker pool to decide whether the serialized graph snapshot is stale.
|
||||
// Mark it modified whenever the copy has actually changed.
|
||||
if (copy_changed) {
|
||||
copy_->set_modified(true);
|
||||
}
|
||||
|
||||
// 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
|
||||
update_last_synced_value();
|
||||
}
|
||||
|
||||
void ProjectCopier::do_node_add(Node *node)
|
||||
{
|
||||
if (dynamic_cast<NodeGroup *>(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->set_caches_enabled(false);
|
||||
|
||||
// Copy cache UUIDs
|
||||
copy->copy_cache_uuids_from(node);
|
||||
|
||||
// Insert into map
|
||||
insert_into_copy_map(node, copy);
|
||||
|
||||
// Keep track of our nodes
|
||||
created_nodes_.append(copy);
|
||||
}
|
||||
|
||||
void ProjectCopier::do_node_remove(Node *node)
|
||||
{
|
||||
// Find our copy and remove it
|
||||
Node *copy = copy_map_.take(node);
|
||||
|
||||
// Disconnect from node's caches
|
||||
emit removed_node(node);
|
||||
|
||||
// Remove from created list
|
||||
created_nodes_.removeOne(copy);
|
||||
|
||||
// Delete it
|
||||
delete copy;
|
||||
}
|
||||
|
||||
void ProjectCopier::do_edge_add(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::connect_edge(our_output,
|
||||
NodeInput(our_input, input.input(), input.element()));
|
||||
}
|
||||
|
||||
void ProjectCopier::do_edge_remove(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::disconnect_edge(our_output,
|
||||
NodeInput(our_input, input.input(), input.element()));
|
||||
}
|
||||
|
||||
void ProjectCopier::do_value_change(const NodeInput &input)
|
||||
{
|
||||
if (dynamic_cast<NodeGroup *>(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::copy_values_of_element(input.node(), our_input, input.input(),
|
||||
input.element());
|
||||
}
|
||||
|
||||
void ProjectCopier::do_value_hint_change(const NodeInput &input)
|
||||
{
|
||||
if (dynamic_cast<NodeGroup *>(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()->get_value_hint_for_input(input.input(), input.element());
|
||||
our_input->set_value_hint_for_input(input.input(), hint, input.element());
|
||||
}
|
||||
|
||||
void ProjectCopier::do_project_setting_change(const QString &key,
|
||||
const QString &value)
|
||||
{
|
||||
copy_->set_setting(key, value);
|
||||
}
|
||||
|
||||
void ProjectCopier::insert_into_copy_map(Node *node, Node *copy)
|
||||
{
|
||||
// Insert into map
|
||||
copy_map_.insert(node, copy);
|
||||
|
||||
// Copy parameters
|
||||
Node::copy_inputs(node, copy, false);
|
||||
|
||||
// Sync Footage proxy state (which is not stored as a Node input)
|
||||
if (Footage *src_footage = dynamic_cast<Footage *>(node)) {
|
||||
if (dynamic_cast<Footage *>(copy)) {
|
||||
connect(src_footage, &Footage::proxy_settings_changed, this,
|
||||
[this, src_footage]() {
|
||||
sync_footage_proxy_settings(src_footage);
|
||||
});
|
||||
sync_footage_proxy_settings(src_footage);
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to node's cache
|
||||
emit added_node(node);
|
||||
}
|
||||
|
||||
void ProjectCopier::sync_footage_proxy_settings(Footage *source)
|
||||
{
|
||||
Footage *copy = get_copy(source);
|
||||
if (!copy) {
|
||||
qWarning() << "ProjectCopier::SyncFootageProxySettings: no copy for"
|
||||
<< source->filename();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug()
|
||||
<< "ProjectCopier::SyncFootageProxySettings:" << source->filename()
|
||||
<< "enabled=" << source->proxy_enabled() << "->"
|
||||
<< copy->proxy_enabled()
|
||||
<< "state=" << ProxyManager::proxy_state_to_string(source->proxy_state());
|
||||
|
||||
copy->set_proxy(source->proxy_path(), source->proxy_state(),
|
||||
source->proxy_video_stream_index(),
|
||||
source->proxy_preset_version(), source->proxy_enabled());
|
||||
|
||||
if (Project *cp = copy->project()) {
|
||||
cp->set_modified(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectCopier::queue_node_add(Node *node)
|
||||
{
|
||||
graph_update_queue_.push_back({ QueuedJob::k_node_added, node, NodeInput(),
|
||||
nullptr, QString(), QString() });
|
||||
update_graph_change_value();
|
||||
}
|
||||
|
||||
void ProjectCopier::queue_node_remove(Node *node)
|
||||
{
|
||||
graph_update_queue_.push_back({ QueuedJob::k_node_removed, node, NodeInput(),
|
||||
nullptr, QString(), QString() });
|
||||
update_graph_change_value();
|
||||
}
|
||||
|
||||
void ProjectCopier::queue_edge_add(Node *output, const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.push_back({ QueuedJob::k_edge_added, nullptr, input,
|
||||
output, QString(), QString() });
|
||||
update_graph_change_value();
|
||||
}
|
||||
|
||||
void ProjectCopier::queue_edge_remove(Node *output, const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.push_back({ QueuedJob::k_edge_removed, nullptr, input,
|
||||
output, QString(), QString() });
|
||||
update_graph_change_value();
|
||||
}
|
||||
|
||||
void ProjectCopier::queue_value_change(const NodeInput &input)
|
||||
{
|
||||
/*for (auto it = graph_update_queue_.begin(); it != graph_update_queue_.end(); ) {
|
||||
if (it->type == QueuedJob::kValueChanged && it->input == input) {
|
||||
it = graph_update_queue_.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}*/
|
||||
|
||||
graph_update_queue_.push_back({ QueuedJob::k_value_changed, nullptr, input,
|
||||
nullptr, QString(), QString() });
|
||||
update_graph_change_value();
|
||||
}
|
||||
|
||||
void ProjectCopier::queue_value_hint_change(const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.push_back({ QueuedJob::k_value_hint_changed, nullptr,
|
||||
input, nullptr, QString(), QString() });
|
||||
update_graph_change_value();
|
||||
}
|
||||
|
||||
void ProjectCopier::queue_project_setting_change(const QString &key,
|
||||
const QString &value)
|
||||
{
|
||||
graph_update_queue_.push_back({ QueuedJob::k_project_setting_changed, nullptr,
|
||||
NodeInput(), nullptr, key, value });
|
||||
update_graph_change_value();
|
||||
}
|
||||
|
||||
void ProjectCopier::update_graph_change_value()
|
||||
{
|
||||
graph_changed_time_.acquire();
|
||||
}
|
||||
|
||||
void ProjectCopier::update_last_synced_value()
|
||||
{
|
||||
last_update_time_.acquire();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user