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:
@@ -86,6 +86,23 @@ void Project::initialize()
|
||||
|
||||
void Project::clear()
|
||||
{
|
||||
is_being_cleared_ = true;
|
||||
|
||||
// Notify observers about each node while it is still fully constructed.
|
||||
// The childEvent() removal path fires from ~Node, when derived-class
|
||||
// members are already destroyed; observers touching the node there
|
||||
// (e.g. querying a Sequence's tracks) used to crash.
|
||||
const QVector<Node *> notify_copy = node_children_;
|
||||
for (Node *node : notify_copy) {
|
||||
if (!node_children_.contains(node)) {
|
||||
// An observer already removed it from the graph
|
||||
continue;
|
||||
}
|
||||
emit node_removed(node);
|
||||
emit node->removed_from_graph(this);
|
||||
node->RemovedFromGraphEvent(this);
|
||||
}
|
||||
|
||||
// By deleting the last nodes first, we assume that nodes that are most important are deleted last
|
||||
// (e.g. Project's ColorManager or ProjectSettingsNode.
|
||||
for (auto it = node_children_.cbegin(); it != node_children_.cend(); it++) {
|
||||
@@ -95,6 +112,8 @@ void Project::clear()
|
||||
while (!node_children_.isEmpty()) {
|
||||
delete node_children_.last();
|
||||
}
|
||||
|
||||
is_being_cleared_ = false;
|
||||
}
|
||||
|
||||
SerializedData Project::load(QXmlStreamReader *reader)
|
||||
@@ -369,6 +388,14 @@ void Project::childEvent(QChildEvent *event)
|
||||
} else if (event->type() == QEvent::ChildRemoved) {
|
||||
node_children_.removeOne(node);
|
||||
|
||||
if (is_being_cleared_) {
|
||||
// Teardown: everything is being destroyed anyway. The
|
||||
// disconnect/emit dance below touches the half-destroyed
|
||||
// child (this fires from ~Node) and has repeatedly crashed;
|
||||
// Qt auto-disconnects the rest when destruction completes.
|
||||
return;
|
||||
}
|
||||
|
||||
// Disconnect signals
|
||||
disconnect(node, &Node::input_connected, this,
|
||||
&Project::input_connected);
|
||||
|
||||
Reference in New Issue
Block a user