diff --git a/app/node/node.cpp b/app/node/node.cpp index bca4017e6..47fceb075 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -47,7 +47,8 @@ Node::Node(bool create_default_output) : can_be_deleted_(true), override_color_(-1), last_change_time_(0), - folder_(nullptr) + folder_(nullptr), + operation_stack_(0) { if (create_default_output) { AddOutput(); @@ -1027,18 +1028,14 @@ void Node::InvalidateCache(const TimeRange &range, const QString &from, int elem void Node::BeginOperation() { - // Ripple through graph - for (const std::pair& output : output_connections_) { - output.second.node()->BeginOperation(); - } + // Increase operation stack + operation_stack_++; } void Node::EndOperation() { - // Ripple through graph - for (const std::pair& output : output_connections_) { - output.second.node()->EndOperation(); - } + // Decrease operation stack + operation_stack_--; } TimeRange Node::InputTimeAdjustment(const QString &, int, const TimeRange &input_time) const @@ -1177,11 +1174,13 @@ Node *Node::CopyNodeInGraph(const Node *node, MultiUndoCommand *command) void Node::SendInvalidateCache(const TimeRange &range, qint64 job_time) { - for (const OutputConnection& conn : output_connections_) { - // Send clear cache signal to the Node - const NodeInput& in = conn.second; + if (GetOperationStack() == 0) { + for (const OutputConnection& conn : output_connections_) { + // Send clear cache signal to the Node + const NodeInput& in = conn.second; - in.node()->InvalidateCache(range, in.input(), in.element(), job_time); + in.node()->InvalidateCache(range, in.input(), in.element(), job_time); + } } } diff --git a/app/node/node.h b/app/node/node.h index c8b4de197..bf29f593c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -816,6 +816,11 @@ protected: void IgnoreHashingFrom(const QString& input_id); + int GetOperationStack() const + { + return operation_stack_; + } + virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled); virtual void SaveCustom(QXmlStreamWriter* writer) const; @@ -1179,6 +1184,8 @@ private: Folder* folder_; + int operation_stack_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 79b093873..3740d311d 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -417,6 +417,10 @@ QVector Track::BlocksAtTimeRange(const TimeRange &range) const void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time) { + if (GetOperationStack() != 0) { + return; + } + TimeRange limited; const Block* b; @@ -431,8 +435,8 @@ void Track::InvalidateCache(const TimeRange& range, const QString& from, int ele limited = TimeRange(qMax(range.in(), b->in()), qMin(range.out(), b->out())); } else { - limited = TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), qMax(last_invalidated_length_, track_length()))); - last_invalidated_length_ = track_length(); + limited = TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), qMax(preop_track_length_, track_length()))); + preop_track_length_ = track_length_; } Node::InvalidateCache(limited, from, element, job_time); @@ -578,6 +582,15 @@ void Track::Hash(const QString &output, QCryptographicHash &hash, const rational } } +void Track::EndOperation() +{ + super::EndOperation(); + + if (track_length_ != midop_track_length_) { + SetLengthInternal(midop_track_length_); + } +} + void Track::SetMuted(bool e) { SetStandardValue(kMutedInput, e); @@ -629,13 +642,13 @@ int Track::GetCacheIndexFromArrayIndex(int index) const void Track::SetLengthInternal(const rational &r, bool invalidate) { - if (r != track_length_) { - // TimeRange will automatically normalize so that the shorter number is the in and the longer - // is the out - TimeRange invalidate_range(track_length_, r); + // Hold track length until operation stack is empty + midop_track_length_ = r; + if (GetOperationStack() == 0 && track_length_ != r) { + TimeRange invalidate_range(track_length_, r); track_length_ = r; - last_invalidated_length_ = qMax(last_invalidated_length_, track_length_); + preop_track_length_ = qMax(preop_track_length_, track_length_); emit TrackLengthChanged(); if (invalidate) { diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 91a1c40cf..4dd6eebd7 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -343,6 +343,8 @@ public: return waveform_; } + virtual void EndOperation() override; + static const double kTrackHeightDefault; static const double kTrackHeightMinimum; static const double kTrackHeightInterval; @@ -425,7 +427,9 @@ private: rational track_length_; - rational last_invalidated_length_; + rational midop_track_length_; + + rational preop_track_length_; double track_height_; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 2d1ba3e0d..6b30cd3cc 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -38,8 +38,7 @@ const uint64_t ViewerOutput::kVideoParamEditMask = VideoParamEdit::kWidthHeight ViewerOutput::ViewerOutput(bool create_default_streams) : video_frame_cache_(this), - audio_playback_cache_(this), - operation_stack_(0) + audio_playback_cache_(this) { AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray)); SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(kVideoParamEditMask)); @@ -218,24 +217,22 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, { Q_UNUSED(element) - if (operation_stack_ == 0) { - if (from == kTextureInput || from == kSamplesInput - || from == kVideoParamsInput || from == kAudioParamsInput) { - TimeRange invalidated_range(qMax(rational(), range.in()), - qMin(GetLength(), range.out())); + if (from == kTextureInput || from == kSamplesInput + || from == kVideoParamsInput || from == kAudioParamsInput) { + TimeRange invalidated_range(qMax(rational(), range.in()), + qMin(GetLength(), range.out())); - if (invalidated_range.in() != invalidated_range.out()) { - if (from == kTextureInput || from == kVideoParamsInput) { - video_frame_cache_.Invalidate(invalidated_range, job_time); - } else { - audio_playback_cache_.Invalidate(invalidated_range, job_time); - } + if (invalidated_range.in() != invalidated_range.out()) { + if (from == kTextureInput || from == kVideoParamsInput) { + video_frame_cache_.Invalidate(invalidated_range, job_time); + } else { + audio_playback_cache_.Invalidate(invalidated_range, job_time); } } - - VerifyLength(); } + VerifyLength(); + super::InvalidateCache(range, from, element, job_time); } @@ -298,10 +295,6 @@ void ViewerOutput::Retranslate() void ViewerOutput::VerifyLength() { - if (operation_stack_ != 0) { - return; - } - NodeTraverser traverser; rational video_length, audio_length, subtitle_length; @@ -364,20 +357,6 @@ rational ViewerOutput::GetCustomLength(Track::Type type) const return rational(); } -void ViewerOutput::BeginOperation() -{ - operation_stack_++; - - super::BeginOperation(); -} - -void ViewerOutput::EndOperation() -{ - operation_stack_--; - - super::EndOperation(); -} - NodeOutput ViewerOutput::GetConnectedTextureOutput() { return GetConnectedOutput(kTextureInput); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 27902a90d..1872df8e8 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -136,10 +136,6 @@ public: virtual void Retranslate() override; - virtual void BeginOperation() override; - - virtual void EndOperation() override; - virtual NodeOutput GetConnectedTextureOutput(); virtual NodeOutput GetConnectedSampleOutput(); diff --git a/app/widget/timelinewidget/timelineundo.h b/app/widget/timelinewidget/timelineundo.h index 1cfbb8df0..88aca1959 100644 --- a/app/widget/timelinewidget/timelineundo.h +++ b/app/widget/timelinewidget/timelineundo.h @@ -1170,7 +1170,6 @@ private: new_block_length = b->length() + operation_movement; } - rational earliest_point_of_change; rational pre_shift; rational post_shift; @@ -1195,7 +1194,7 @@ private: // As an insertion, we will shift from the gap's in to the gap's out pre_shift = gap->in(); post_shift = gap->out(); - earliest_point_of_change = gap->in(); + working_data.earliest_point_of_change = gap->in(); } else { // As a removal, we will shift from the gap's out to the gap's in pre_shift = gap->out(); @@ -1212,7 +1211,7 @@ private: if (redo) { // The earliest point changes will happen is at the start of this block - earliest_point_of_change = b->in(); + working_data.earliest_point_of_change = b->in(); // As a removal, we will be shifting from the out point to the in point pre_shift = b->out(); @@ -1228,7 +1227,7 @@ private: track->InsertBlockAfter(b, working_data.removed_gap_after); // The earliest point changes will happen is at the start of this block - earliest_point_of_change = b->in(); + working_data.earliest_point_of_change = b->in(); // As an insert, we will be shifting from the block's in point to its out point pre_shift = b->in(); @@ -1242,7 +1241,7 @@ private: if (movement_mode_ == Timeline::kTrimIn) { // The earliest point changes will occur is in point of this bloc - earliest_point_of_change = b->in(); + working_data.earliest_point_of_change = b->in(); // Undo the trim in inversion we do above, this will still be inverted accurately for // undoing where appropriate @@ -1260,7 +1259,7 @@ private: } else { // The earliest point changes will occur is the out point if trimming out or the in point // if trimming in - earliest_point_of_change = b->out(); + working_data.earliest_point_of_change = b->out(); // The latest out before the ripple is this block's current out point pre_shift = b->out(); @@ -1274,17 +1273,10 @@ private: } - track->EndOperation(); - working_data_.insert(it.key(), working_data); pre_latest_out = qMax(pre_latest_out, pre_shift); post_latest_out = qMax(post_latest_out, post_shift); - - if (!all_tracks_unlocked_) { - // If we're not shifting, the whole track must get invalidated - track->Node::InvalidateCache(TimeRange(earliest_point_of_change, RATIONAL_MAX), Track::kBlockInput); - } } if (all_tracks_unlocked_) { @@ -1295,6 +1287,17 @@ private: track_list_->parent()->ShiftAudioCache(pre_latest_out, post_latest_out); } } + + for (auto it=working_data_.cbegin(); it!=working_data_.cend(); it++) { + Track* track = it.key(); + + track->EndOperation(); + + if (!all_tracks_unlocked_) { + // If we're not shifting, the whole track must get invalidated + track->Node::InvalidateCache(TimeRange(it.value().earliest_point_of_change, RATIONAL_MAX), Track::kBlockInput); + } + } } TrackList* track_list_; @@ -1307,6 +1310,7 @@ private: GapBlock* created_gap = nullptr; Block* removed_gap_after; rational old_length; + rational earliest_point_of_change; }; QHash working_data_; @@ -1650,12 +1654,12 @@ public: virtual void redo() override { - track_->BeginOperation(); - - // Invalidate the range inhabited by this block - TimeRange invalidate_range(block_->in(), block_->out()); - if (block_->next()) { + track_->BeginOperation(); + + // Invalidate the range inhabited by this block + TimeRange invalidate_range(block_->in(), block_->out()); + // Block has a next, which means it's NOT at the end of the sequence and thus requires a gap rational new_gap_length = block_->length(); @@ -1704,6 +1708,10 @@ public: position_command_->redo(); } + track_->EndOperation(); + + track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput); + } else { // Block is at the end of the track, simply remove it @@ -1719,49 +1727,51 @@ public: // Remove block in question track_->RippleRemoveBlock(block_); } - - track_->EndOperation(); - - track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput); } virtual void undo() override { - track_->BeginOperation(); + if (our_gap_ || existing_gap_) { + track_->BeginOperation(); - if (our_gap_) { + if (our_gap_) { - // We made this gap, simply swap our gap back - track_->ReplaceBlock(our_gap_, block_); - our_gap_->setParent(&memory_manager_); + // We made this gap, simply swap our gap back + track_->ReplaceBlock(our_gap_, block_); + our_gap_->setParent(&memory_manager_); - position_command_->undo(); + position_command_->undo(); - } else if (existing_gap_) { - - // If we're here, assume that we extended an existing gap - rational original_gap_length = existing_gap_->length() - block_->length(); - - // If we merged two gaps together, restore the second one now - if (existing_merged_gap_) { - original_gap_length -= existing_merged_gap_->length(); - existing_merged_gap_->setParent(track_->parent()); - track_->InsertBlockAfter(existing_merged_gap_, existing_gap_); - existing_merged_gap_ = nullptr; - } - - // Restore original block - if (existing_gap_precedes_) { - track_->InsertBlockAfter(block_, existing_gap_); } else { - track_->InsertBlockBefore(block_, existing_gap_); + + // If we're here, assume that we extended an existing gap + rational original_gap_length = existing_gap_->length() - block_->length(); + + // If we merged two gaps together, restore the second one now + if (existing_merged_gap_) { + original_gap_length -= existing_merged_gap_->length(); + existing_merged_gap_->setParent(track_->parent()); + track_->InsertBlockAfter(existing_merged_gap_, existing_gap_); + existing_merged_gap_ = nullptr; + } + + // Restore original block + if (existing_gap_precedes_) { + track_->InsertBlockAfter(block_, existing_gap_); + } else { + track_->InsertBlockBefore(block_, existing_gap_); + } + + // Restore gap's original length + existing_gap_->set_length_and_media_out(original_gap_length); + + existing_gap_ = nullptr; + } - // Restore gap's original length - existing_gap_->set_length_and_media_out(original_gap_length); - - existing_gap_ = nullptr; + track_->EndOperation(); + track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput); } else { // Our gap and existing gap were both null, our block must have been at the end and thus @@ -1778,10 +1788,6 @@ public: track_->AppendBlock(block_); } - - track_->EndOperation(); - - track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput); } private: