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:
@@ -462,6 +462,16 @@ ExportDialog::ExportDialog(OakEngineNode *viewer_node, bool stills_only_mode,
|
||||
subtitle_tab_->setEnabled(subtitles_enabled_->isChecked());
|
||||
}
|
||||
|
||||
ExportDialog::~ExportDialog()
|
||||
{
|
||||
// Raw C-API subscription carries `this` as userdata; not covered by
|
||||
// Qt's auto-disconnect.
|
||||
if (viewer_sub_ > 0) {
|
||||
oakengine_event_unsubscribe(viewer_sub_);
|
||||
viewer_sub_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Rational ExportDialog::get_selected_timebase() const
|
||||
{
|
||||
return video_tab_->get_selected_frame_rate().flipped();
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
: ExportDialog(viewer_node, false, parent)
|
||||
{
|
||||
}
|
||||
~ExportDialog() override;
|
||||
|
||||
Rational get_selected_timebase() const;
|
||||
void set_selected_timebase(const Rational &r);
|
||||
|
||||
@@ -110,6 +110,14 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const oak::Input &input
|
||||
&NodeParamViewConnectedLabel::set_value_tree_visible);
|
||||
}
|
||||
|
||||
NodeParamViewConnectedLabel::~NodeParamViewConnectedLabel()
|
||||
{
|
||||
// Raw C-API subscription carries `this` as userdata; not covered by
|
||||
// Qt's auto-disconnect. Unsubscribe or the next playhead event calls
|
||||
// into a dead object.
|
||||
set_viewer_node(nullptr);
|
||||
}
|
||||
|
||||
void NodeParamViewConnectedLabel::set_viewer_node(OakEngineNode *viewer)
|
||||
{
|
||||
if (viewer_) {
|
||||
|
||||
@@ -37,6 +37,7 @@ class NodeParamViewConnectedLabel : public QWidget {
|
||||
public:
|
||||
NodeParamViewConnectedLabel(const oak::Input &input,
|
||||
QWidget *parent = nullptr);
|
||||
~NodeParamViewConnectedLabel() override;
|
||||
|
||||
void set_viewer_node(OakEngineNode *viewer);
|
||||
|
||||
|
||||
@@ -37,8 +37,9 @@ static oak::Input ResolveGroupInput(const oak::Input &input)
|
||||
char input_id[256];
|
||||
int element = input.element();
|
||||
const QByteArray utf = input.input_id().toUtf8();
|
||||
memcpy(input_id, utf.constData(), qMin<int>(sizeof(input_id) - 1, utf.size()));
|
||||
input_id[sizeof(input_id) - 1] = '\0';
|
||||
const int len = qMin<int>(sizeof(input_id) - 1, utf.size());
|
||||
memcpy(input_id, utf.constData(), len);
|
||||
input_id[len] = '\0';
|
||||
// WRAPPER-GAP: oakengine_group_resolve_input (group API has no oak:: wrapper)
|
||||
if (oakengine_group_resolve_input(
|
||||
node, input_id, element,
|
||||
|
||||
@@ -157,6 +157,20 @@ NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align,
|
||||
show_buttons_from_keyframe_enable(false);
|
||||
}
|
||||
|
||||
NodeParamViewKeyframeControl::~NodeParamViewKeyframeControl()
|
||||
{
|
||||
// Raw C-API subscription carries `this` as userdata; it is not covered
|
||||
// by Qt's auto-disconnect. Without this, a playhead event delivered
|
||||
// after destruction calls update_state() on a dead object.
|
||||
if (viewer_sub_ > 0) {
|
||||
oakengine_event_unsubscribe(viewer_sub_);
|
||||
viewer_sub_ = 0;
|
||||
}
|
||||
// Drop the keyframe_* bridge subscriptions too (same raw-userdata
|
||||
// mechanism underneath).
|
||||
set_input(oak::Input());
|
||||
}
|
||||
|
||||
void NodeParamViewKeyframeControl::set_input(const oak::Input &input)
|
||||
{
|
||||
if (input_.is_valid()) {
|
||||
|
||||
@@ -41,6 +41,7 @@ public:
|
||||
: NodeParamViewKeyframeControl(true, parent)
|
||||
{
|
||||
}
|
||||
~NodeParamViewKeyframeControl() override;
|
||||
|
||||
const oak::Input &get_connected_input() const
|
||||
{
|
||||
|
||||
@@ -142,9 +142,9 @@ static bool ResolveGroupInput(oak::Input *input)
|
||||
char input_id[256];
|
||||
int element = input->element();
|
||||
const QByteArray utf = input->input_id().toUtf8();
|
||||
memcpy(input_id, utf.constData(),
|
||||
qMin<int>(sizeof(input_id) - 1, utf.size()));
|
||||
input_id[sizeof(input_id) - 1] = '\0';
|
||||
const int len = qMin<int>(sizeof(input_id) - 1, utf.size());
|
||||
memcpy(input_id, utf.constData(), len);
|
||||
input_id[len] = '\0';
|
||||
if (!oakengine_node_group_get_inner(&node, input_id, sizeof(input_id),
|
||||
&element)) {
|
||||
return false;
|
||||
|
||||
@@ -255,6 +255,12 @@ bool NodeViewContext::child_input_disconnected(OakEngineNode *output,
|
||||
for (int i = 0; i < edges_.size(); i++) {
|
||||
NodeViewEdge *e = edges_.at(i);
|
||||
if (e->output() == oak::Node(output) && e->input() == input) {
|
||||
if (qEnvironmentVariableIsSet("OAK_DEBUG_EDGES")) {
|
||||
qWarning("EDGE-DEBUG: edge removed: %p -> %p (%s,%d) ctx=%p",
|
||||
(void *)output, (void *)input.node_handle(),
|
||||
qPrintable(input.input_id()), input.element(),
|
||||
(void *)this);
|
||||
}
|
||||
delete e;
|
||||
edges_.removeAt(i);
|
||||
return true;
|
||||
@@ -413,6 +419,11 @@ void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
|
||||
void NodeViewContext::add_node_internal(OakEngineNode *node, NodeViewItem *item)
|
||||
{
|
||||
if (qEnvironmentVariableIsSet("OAK_DEBUG_EDGES")) {
|
||||
qWarning("EDGE-DEBUG: node added to ctx %p: %p", (void *)this,
|
||||
(void *)node);
|
||||
}
|
||||
|
||||
node_subs_[node].append(bridge_->subscribe(
|
||||
reinterpret_cast<void *>(node),
|
||||
OAKENGINE_EVENT_NODE_INPUT_CONNECTED));
|
||||
@@ -436,6 +447,11 @@ void NodeViewContext::add_node_internal(OakEngineNode *node, NodeViewItem *item)
|
||||
oak::Input ai(conn.node.handle(), conn.input_id, conn.element);
|
||||
add_edge_internal(node, ai, item,
|
||||
other_item->get_item_for_input(ai));
|
||||
} else if (qEnvironmentVariableIsSet("OAK_DEBUG_EDGES")) {
|
||||
qWarning("EDGE-DEBUG: out-edge skipped (no item for %p): %p -> %p (%s) ctx=%p",
|
||||
(void *)conn.node.handle(), (void *)node,
|
||||
(void *)conn.node.handle(),
|
||||
qPrintable(conn.input_id), (void *)this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,6 +465,11 @@ void NodeViewContext::add_node_internal(OakEngineNode *node, NodeViewItem *item)
|
||||
oak::Input ai(conn.node.handle(), conn.input_id, conn.element);
|
||||
add_edge_internal(conn.source_node.handle(), ai, other_item,
|
||||
item->get_item_for_input(ai));
|
||||
} else if (qEnvironmentVariableIsSet("OAK_DEBUG_EDGES")) {
|
||||
qWarning("EDGE-DEBUG: in-edge skipped (no item for %p): %p -> %p (%s) ctx=%p",
|
||||
(void *)conn.source_node.handle(),
|
||||
(void *)conn.source_node.handle(), (void *)node,
|
||||
qPrintable(conn.input_id), (void *)this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -461,6 +482,12 @@ void NodeViewContext::add_edge_internal(OakEngineNode *output, const oak::Input
|
||||
return;
|
||||
}
|
||||
|
||||
if (qEnvironmentVariableIsSet("OAK_DEBUG_EDGES")) {
|
||||
qWarning("EDGE-DEBUG: edge added: %p -> %p (%s,%d) ctx=%p", (void *)output,
|
||||
(void *)input.node_handle(), qPrintable(input.input_id()),
|
||||
input.element(), (void *)this);
|
||||
}
|
||||
|
||||
NodeViewEdge *edge_ui = new NodeViewEdge(oak::Node(output), input, from, to, this);
|
||||
|
||||
edge_ui->adjust();
|
||||
|
||||
@@ -110,7 +110,7 @@ static SampleBuffer preview_req_to_sample_buffer(OakEnginePreviewRequest *req)
|
||||
oakcore_audioparams_free(ap);
|
||||
|
||||
// Determine sample count from first channel
|
||||
int n = oakengine_preview_request_get_audio_samples(req, 0, nullptr, 1 << 30);
|
||||
int n = oakengine_preview_request_get_audio_sample_count(req);
|
||||
if (n > 0) {
|
||||
oakcore_samplebuffer_set_sample_count(sb, size_t(n));
|
||||
oakcore_samplebuffer_allocate(sb);
|
||||
|
||||
Reference in New Issue
Block a user