- serializer resolves node types through the factory's dynamic (runtime-registered OpenFX) entries, so a project carrying plugin nodes loads again (was: "unknown node type"); covered by a new CI-gated round-trip test driving the real fixture plugin - built-in effect nodes expose their inputs as inspector parameters like the C++ parameter editor: localized input names from the behavior, combo option tables via the new NodeBehavior::input_combo_strings (16 nodes, string-for-string from the C++ set_combo_box_strings), connection/data inputs excluded - effect library: live drag-and-drop — onto the inspector's effect stack (lands at the indicator position) and onto the node editor canvas (creates the node at the drop point); double-click still appends to the selected clip - inspector parameter controls are no longer recreated per render (gpui stack view caches them per effect), so sliders drag and checkboxes click; the view observes the engine and silently re-syncs values (undo/redo land on the widgets) - timeline: left-press selects clips (plain/keep-multi/Ctrl-Cmd toggle); clip moves clamp the shared delta so no clip of a linked group lands before frame 0 instead of failing with "invalid move target" - oakplugin: createInstance-rejected instances skip the destroyInstance notification (the plugin never owned them); vendor-suite fetchSuite misses moved behind OAK_OFX_TRACE; the worker logs the discovered/ registered plugin counts - CI: the OFX probe step also runs the serialization round-trip test - gpui submodule: params view caching, clip click-select, library drag payload, graph_position_at
75 lines
3.0 KiB
Rust
75 lines
3.0 KiB
Rust
// Oak Video Editor - Non-Linear Video Editor
|
|
// Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
|
|
|
//! OFX plugin project serialization round-trip (CI-gated): a project that
|
|
//! carries a plugin node must save to XML and load back with the same
|
|
//! plugin type id — the serializer resolves plugin types through the
|
|
//! factory's dynamic (runtime-registered) entries.
|
|
//!
|
|
//! Gated on `OAK_OFX_FIXTURE_DIR` pointing at a directory with a built
|
|
//! `OakCiTest.ofx.bundle` (see `tests/fixtures/build_fixture.sh`); the
|
|
//! test skips silently when the variable is unset so plain `cargo test`
|
|
//! runs stay hermetic. The CI workflow builds the fixture and sets it.
|
|
|
|
use oakplugin::host::Host;
|
|
|
|
/// The fixture plugin's type id (tests/fixtures/ci_test_plugin.c).
|
|
const FIXTURE_TYPE_ID: &str = "rs.oak.CiTestPlugin";
|
|
|
|
#[test]
|
|
fn plugin_node_survives_save_load_roundtrip() {
|
|
let Ok(fixture_dir) = std::env::var("OAK_OFX_FIXTURE_DIR") else {
|
|
eprintln!("OAK_OFX_FIXTURE_DIR unset; skipping the OFX round-trip test");
|
|
return;
|
|
};
|
|
let host = Host::global();
|
|
host.cache
|
|
.scan_path(std::path::Path::new(&fixture_dir))
|
|
.expect("fixture dir scans");
|
|
let registered = oakplugin::node_factory::register_plugin_nodes();
|
|
assert!(
|
|
registered.iter().any(|id| id == FIXTURE_TYPE_ID),
|
|
"the fixture plugin registered (got {registered:?})"
|
|
);
|
|
|
|
// Build a project carrying one plugin node.
|
|
let project = oaknode::project::Project::new();
|
|
let node_id = {
|
|
let mut p = project.lock().unwrap_or_else(|e| e.into_inner());
|
|
let (core, behavior) = oaknode::factory::Factory::global()
|
|
.create_any(FIXTURE_TYPE_ID)
|
|
.expect("the fixture type resolves through the factory");
|
|
p.graph.add_node(core, behavior)
|
|
};
|
|
|
|
// Save, wipe, reload: the type id must resolve again (this is the path
|
|
// that used to fail with "unknown node type" for plugin nodes).
|
|
let xml = {
|
|
let p = project.lock().unwrap_or_else(|e| e.into_inner());
|
|
oaknode::serializer::save(&p).expect("project saves")
|
|
};
|
|
let loaded = oaknode::serializer::load(&xml).expect("project with a plugin node loads");
|
|
let p = loaded.lock().unwrap_or_else(|e| e.into_inner());
|
|
let mut found = false;
|
|
for id in p.graph.node_ids() {
|
|
let entry = p.graph.get(id).expect("listed node exists");
|
|
if entry.behavior.type_id() == FIXTURE_TYPE_ID {
|
|
found = true;
|
|
}
|
|
}
|
|
assert!(found, "the plugin node survived the round trip (node {node_id:?})");
|
|
}
|