fix: production defects and observability found by the test suites

- ForceParams: hand-written Default with force_format = -1 (was 0 = U8,
  which pushed the F32 pipeline into the U8 scale path).
- Plugin clip output: write CPU pixels back into the target texture
  instead of the deep clone returned by texture_get_frame.
- Display ICC: probe the Debian/Ubuntu icc-profiles-free path.
- RippleInfo: public constructor and accessors so the ripple command is
  reachable from integration tests.
- MockEngine: record effect-parameter and push-button attempts so the
  params-view routing tests are falsifiable.
- OFX params: log rejected parameter writes instead of discarding them.
- Manager docs: state the synchronous codec-submission contract.
This commit is contained in:
2026-09-22 20:54:03 +08:00
parent f188bc79e7
commit 18abdec423
6 changed files with 191 additions and 25 deletions
+74
View File
@@ -493,6 +493,17 @@ pub struct MockEngine {
effects: Vec<MockEffect>,
/// Id allocator for effects added at runtime.
next_effect_id: u64,
/// Parameter-write attempts from the params view (`input_id` + value
/// debug), recorded even when the call fails, so the routing tests are
/// falsifiable (review §10.2: previously the mock swallowed every write
/// and the tests could not observe it).
effect_param_attempts: Mutex<Vec<(EffectId, String, String)>>,
/// Push-button attempts from the params view.
effect_push_attempts: Mutex<Vec<(EffectId, String)>>,
/// Result returned by `set_effect_param`/`effect_push_button` (default
/// `Err`: the demo models an unsupported node; tests can flip it to cover
/// the success path).
effect_param_ok: bool,
/// Whether the program monitor is playing (mirrors the clock; kept here
/// because the audio-meter data source has no `App` to read the clock).
program_playing: bool,
@@ -588,6 +599,29 @@ pub struct MockEngine {
impl MockEngine {
/// Builds the demo project: 第一稿.ove, one HD sequence, four tracks.
/// The parameter-write attempts the params view routed to this engine
/// (`(effect, input_id, value debug)`), in call order.
pub fn effect_param_attempts(&self) -> Vec<(EffectId, String, String)> {
self.effect_param_attempts
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
/// The push-button attempts the params view routed to this engine.
pub fn effect_push_attempts(&self) -> Vec<(EffectId, String)> {
self.effect_push_attempts
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
/// Flip the result of `set_effect_param`/`effect_push_button` (default
/// `Err`); tests use `true` to cover the accepted path.
pub fn set_effect_param_ok(&mut self, ok: bool) {
self.effect_param_ok = ok;
}
pub fn demo(cx: &mut Context<Self>) -> Self {
let rate = VideoFormat::hd_1080p25().rate;
let clip =
@@ -862,6 +896,9 @@ impl MockEngine {
},
],
next_effect_id: 4,
effect_param_attempts: Mutex::new(Vec::new()),
effect_push_attempts: Mutex::new(Vec::new()),
effect_param_ok: false,
program_playing: false,
selected_item: None,
meter_phase: 0,
@@ -1681,6 +1718,43 @@ impl AppEngine for MockEngine {
})
}
fn set_effect_param(
&mut self,
effect: EffectId,
input_id: &str,
value: oak_node::value::NodeValue,
cx: &mut Context<Self>,
) -> Result<(), String> {
self.effect_param_attempts
.lock()
.unwrap_or_else(|e| e.into_inner())
.push((effect, input_id.to_string(), format!("{value:?}")));
if self.effect_param_ok {
cx.notify();
Ok(())
} else {
Err("the demo engine does not apply effect parameters".into())
}
}
fn effect_push_button(
&mut self,
effect: EffectId,
input_id: &str,
cx: &mut Context<Self>,
) -> Result<(), String> {
self.effect_push_attempts
.lock()
.unwrap_or_else(|e| e.into_inner())
.push((effect, input_id.to_string()));
if self.effect_param_ok {
cx.notify();
Ok(())
} else {
Err("the demo engine does not trigger effect push buttons".into())
}
}
fn add_node_at(
&mut self,
type_id: &str,
+20 -8
View File
@@ -873,7 +873,9 @@ fn wire_controls<E: AppEngine>(view: &OfxParamsView<E>, cx: &mut Context<OfxPara
_ => NodeValue::Float(value.to_f64()),
};
engine.update(cx, |engine, cx| {
let _ = engine.set_effect_param(effect, &input_id, nv, cx);
if let Err(err) = engine.set_effect_param(effect, &input_id, nv, cx) {
eprintln!("[ofx params] set {input_id:?} failed: {err}");
}
});
}
},
@@ -886,7 +888,9 @@ fn wire_controls<E: AppEngine>(view: &OfxParamsView<E>, cx: &mut Context<OfxPara
let CheckBoxEvent::Toggled { state, .. } = event;
let nv = NodeValue::Boolean(*state == CheckState::Checked);
engine.update(cx, |engine, cx| {
let _ = engine.set_effect_param(effect, &input_id, nv, cx);
if let Err(err) = engine.set_effect_param(effect, &input_id, nv, cx) {
eprintln!("[ofx params] set {input_id:?} failed: {err}");
}
});
})
.detach();
@@ -912,7 +916,9 @@ fn wire_controls<E: AppEngine>(view: &OfxParamsView<E>, cx: &mut Context<OfxPara
_ => NodeValue::Combo(*value as i64),
};
engine.update(cx, |engine, cx| {
let _ = engine.set_effect_param(effect, &input_id, nv, cx);
if let Err(err) = engine.set_effect_param(effect, &input_id, nv, cx) {
eprintln!("[ofx params] set {input_id:?} failed: {err}");
}
});
})
.detach();
@@ -1023,12 +1029,14 @@ fn wire_controls<E: AppEngine>(view: &OfxParamsView<E>, cx: &mut Context<OfxPara
.collect();
let json = oak_plugin::param_curve::curves_to_json(&curves);
engine.update(cx, |engine, cx| {
let _ = engine.set_effect_param(
if let Err(err) = engine.set_effect_param(
effect,
&input_id,
NodeValue::Text(json),
cx,
);
) {
eprintln!("[ofx params] set {input_id:?} failed: {err}");
}
});
},
)
@@ -1105,7 +1113,9 @@ impl<E: AppEngine> Render for OfxParamsView<E> {
.child(button_label)
.on_click(move |_event: &ClickEvent, _window, cx| {
engine.update(cx, |engine, cx| {
let _ = engine.effect_push_button(effect, &input_id, cx);
if let Err(err) = engine.effect_push_button(effect, &input_id, cx) {
eprintln!("[ofx params] push {input_id:?} failed: {err}");
}
});
})
.into_any_element()
@@ -1204,12 +1214,14 @@ impl<E: AppEngine> Render for OfxParamsView<E> {
.on_click(move |_event: &ClickEvent, _window, cx| {
let text = editor_commit.read(cx).as_str().to_string();
engine.update(cx, |engine, cx| {
let _ = engine.set_effect_param(
if let Err(err) = engine.set_effect_param(
effect,
&input_id,
NodeValue::Text(text),
cx,
);
) {
eprintln!("[ofx params] set {input_id:?} failed: {err}");
}
});
}),
)