fix: node graph edge display, teardown crashes, and event/audio lifetime bugs

- capi: oakengine_node_output_connection_at/_at_ex returned the source
  node as the connection destination; the actual destination is
  conn.second.node(). Out-edge enumeration was useless, so the node
  view could only draw in-edges and randomly lost whichever edges
  needed the out-edge path (random per context build order).
  Regression test in oakengine_node_test
- project teardown: Project::clear() pre-notifies node removal while
  nodes are fully constructed (observers used to crash on
  half-destroyed nodes); childEvent suppresses the removal dance while
  clearing; Node::disconnect_all/disconnect_edge get a silent mode for
  teardown so no invalidation/events touch dying members
  (is_being_cleared); ClipBlock marker disconnect guarded against
  dead viewer/markers; ProjectCopier and PreviewAutoCacher drop
  project references on Project::destroyed instead of disconnecting
  dead objects at shutdown
- preview: add oakengine_preview_request_get_audio_sample_count; the
  viewer queried sample count by passing nullptr to get_audio_samples
  which rejects it, so all playback audio was silently dropped
- app: fix unterminated input-id memcpy in ResolveGroupInput
  (nodeparamviewitem, widgetbridge) that corrupted every parameter id
- app: unsubscribe raw C-API event subscriptions in destructors of
  NodeParamViewKeyframeControl, NodeParamViewConnectedLabel and
  ExportDialog; playhead events used to fire into dead widgets
  (crash when dragging the playhead)
- tests: preview request roundtrip (video frame + audio range) and
  free-while-active teardown coverage; env-gated OAK_DEBUG_EDGES /
  OAK_DEBUG_INVALID_INPUT diagnostics
- docs: investigation notes in docs/zh/
This commit is contained in:
2026-08-02 14:29:49 +08:00
parent 30853cbcfd
commit a59c33715f
25 changed files with 441 additions and 32 deletions
+24 -18
View File
@@ -435,30 +435,36 @@ void ClipBlock::invalidate_cache(const TimeRange &range, const QString &from,
viewers.isEmpty() ? nullptr : viewers.first();
if (new_connected_viewer != connected_viewer_) {
TimelineMarkerList *old_markers =
connected_viewer_ ? connected_viewer_->get_markers() : nullptr;
if (old_markers) {
disconnect(old_markers, &TimelineMarkerList::marker_added, this,
&ClipBlock::preview_changed);
disconnect(old_markers, &TimelineMarkerList::marker_removed,
this, &ClipBlock::preview_changed);
disconnect(old_markers, &TimelineMarkerList::marker_modified,
this, &ClipBlock::preview_changed);
}
if (connected_viewer_) {
disconnect(connected_viewer_->get_markers(),
&TimelineMarkerList::marker_added, this,
&ClipBlock::preview_changed);
disconnect(connected_viewer_->get_markers(),
&TimelineMarkerList::marker_removed, this,
&ClipBlock::preview_changed);
disconnect(connected_viewer_->get_markers(),
&TimelineMarkerList::marker_modified, this,
&ClipBlock::preview_changed);
disconnect(connected_viewer_, &ViewerOutput::destroyed, this,
nullptr);
}
connected_viewer_ = new_connected_viewer;
TimelineMarkerList *new_markers =
connected_viewer_ ? connected_viewer_->get_markers() : nullptr;
if (new_markers) {
connect(new_markers, &TimelineMarkerList::marker_added, this,
&ClipBlock::preview_changed);
connect(new_markers, &TimelineMarkerList::marker_removed, this,
&ClipBlock::preview_changed);
connect(new_markers, &TimelineMarkerList::marker_modified,
this, &ClipBlock::preview_changed);
}
if (connected_viewer_) {
connect(connected_viewer_->get_markers(),
&TimelineMarkerList::marker_added, this,
&ClipBlock::preview_changed);
connect(connected_viewer_->get_markers(),
&TimelineMarkerList::marker_removed, this,
&ClipBlock::preview_changed);
connect(connected_viewer_->get_markers(),
&TimelineMarkerList::marker_modified, this,
&ClipBlock::preview_changed);
connect(connected_viewer_, &ViewerOutput::destroyed, this,
[this]() { connected_viewer_ = nullptr; });
}
}