timeline: heavily optimized and improved nearly all timeline optimizations

Features a lot of timeline-related cache optimizations as well as general
optimizations and improvements in timeline behavior. Should improve
usability significantly.
This commit is contained in:
itsmattkc
2020-06-01 04:42:59 +10:00
parent 93f8b4f79c
commit c2e47a296c
35 changed files with 1496 additions and 939 deletions
+430 -6
View File
@@ -21,8 +21,9 @@
#include "undo.h"
#include "core.h"
#include "node/graph.h"
#include "node/block/clip/clip.h"
#include "node/block/transition/transition.h"
#include "node/graph.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
@@ -342,6 +343,11 @@ TrackPlaceBlockCommand::TrackPlaceBlockCommand(TrackList *timeline, int track, B
insert_ = block;
}
Project *TrackPlaceBlockCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(static_cast<ViewerOutput*>(timeline_->parent())->parent())->project();
}
void TrackPlaceBlockCommand::redo_internal()
{
added_track_count_ = 0;
@@ -425,8 +431,7 @@ Project *BlockSplitCommand::GetRelevantProject() const
void BlockSplitCommand::redo_internal()
{
// FIXME: Reintroduce this optimization when block waveforms update automatically
// track_->BlockInvalidateCache();
track_->BlockInvalidateCache();
static_cast<NodeGraph*>(block_->parent())->AddNode(new_block_);
Node::CopyInputs(block_, new_block_);
@@ -444,12 +449,16 @@ void BlockSplitCommand::redo_internal()
NodeParam::ConnectEdge(new_block_->output(), transition);
}
// track_->UnblockInvalidateCache();
if (block_->type() == Block::kClip) {
static_cast<ClipBlock*>(new_block_)->set_waveform(static_cast<ClipBlock*>(block_)->waveform().Cut(new_length_));
}
track_->UnblockInvalidateCache();
}
void BlockSplitCommand::undo_internal()
{
// track_->BlockInvalidateCache();
track_->BlockInvalidateCache();
block_->set_length_and_media_out(old_length_);
track_->RippleRemoveBlock(new_block_);
@@ -461,7 +470,11 @@ void BlockSplitCommand::undo_internal()
NodeParam::ConnectEdge(block_->output(), transition);
}
// track_->UnblockInvalidateCache();
if (block_->type() == Block::kClip) {
static_cast<ClipBlock*>(block_)->waveform().Append(static_cast<ClipBlock*>(new_block_)->waveform());
}
track_->UnblockInvalidateCache();
}
Block *BlockSplitCommand::new_block()
@@ -593,6 +606,7 @@ Project *BlockSplitPreservingLinksCommand::GetRelevantProject() const
return static_cast<Sequence*>(blocks_.first()->parent())->project();
}
/*
TrackCleanGapsCommand::TrackCleanGapsCommand(TrackList *track_list, int index, QUndoCommand *parent) :
UndoCommand(parent),
track_list_(track_list),
@@ -686,6 +700,7 @@ void TrackCleanGapsCommand::undo_internal()
merged_gaps_.clear();
}
*/
BlockSetSpeedCommand::BlockSetSpeedCommand(Block *block, const rational &new_speed, QUndoCommand *parent) :
UndoCommand(parent),
@@ -923,4 +938,413 @@ void BlockEnableDisableCommand::undo_internal()
block_->set_enabled(old_enabled_);
}
BlockTrimCommand::BlockTrimCommand(TrackOutput* track, Block *block, rational new_length, Timeline::MovementMode mode, QUndoCommand *command) :
UndoCommand(command),
track_(track),
block_(block),
old_length_(block->length()),
new_length_(new_length),
mode_(mode),
adjacent_(nullptr),
we_created_adjacent_(false),
allow_nongap_trimming_(false)
{
}
Project *BlockTrimCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(block_->parent())->project();
}
void BlockTrimCommand::redo_internal()
{
track_->BlockInvalidateCache();
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
rational trim_diff = old_length_ - new_length_;
TimeRange invalidate_range;
if (mode_ == Timeline::kTrimIn) {
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff);
block_->set_length_and_media_in(new_length_);
adjacent_ = block_->previous();
} else {
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff);
block_->set_length_and_media_out(new_length_);
adjacent_ = block_->next();
}
if (trim_diff > rational()) {
// If trimming SHORTER, we'll need to create/modify a gap
if (adjacent_ && (adjacent_->type() == Block::kGap || allow_nongap_trimming_)) {
// A gap (or equivalent) exists, simply increase the size of it
if (mode_ == Timeline::kTrimIn) {
adjacent_->set_length_and_media_out(adjacent_->length() + trim_diff);
} else {
adjacent_->set_length_and_media_in(adjacent_->length() + trim_diff);
}
} else {
// Don't create a gap if the trim was at the end of the sequence (which would be indicated by
// the mode being "trim out" and "block_->next()" being null.
if (mode_ == Timeline::kTrimIn || block_->next()) {
// We must create a gap
we_created_adjacent_ = true;
adjacent_ = new GapBlock();
adjacent_->set_length_and_media_out(trim_diff);
static_cast<NodeGraph*>(track_->parent())->AddNode(adjacent_);
if (mode_ == Timeline::kTrimIn) {
track_->InsertBlockBefore(adjacent_, block_);
} else {
track_->InsertBlockAfter(adjacent_, block_);
}
}
}
if (block_->type() == Block::kClip) {
if (mode_ == Timeline::kTrimIn) {
static_cast<ClipBlock*>(block_)->waveform().TrimIn(trim_diff);
} else {
static_cast<ClipBlock*>(block_)->waveform().TrimOut(trim_diff);
}
}
} else {
if (adjacent_) {
// If trimming LONGER, we'll need to trim the adjacent
// (assume if there's no adjacent, we're at the end of the timeline and do nothing)
if (mode_ == Timeline::kTrimIn) {
adjacent_->set_length_and_media_out(adjacent_->length() + trim_diff);
} else {
adjacent_->set_length_and_media_in(adjacent_->length() + trim_diff);
}
}
if (block_->type() == Block::kClip) {
if (mode_ == Timeline::kTrimIn) {
static_cast<ClipBlock*>(block_)->waveform().PrependSilence(-trim_diff);
} else {
static_cast<ClipBlock*>(block_)->waveform().AppendSilence(-trim_diff);
}
}
}
track_->UnblockInvalidateCache();
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
void BlockTrimCommand::undo_internal()
{
track_->BlockInvalidateCache();
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
rational trim_diff = old_length_ - new_length_;
if (trim_diff > rational()) {
// If trimmed SHORTER, we need to unadjust the gap
if (we_created_adjacent_) {
// If we created a gap, just remove it straight up
track_->RippleRemoveBlock(adjacent_);
TakeNodeFromParentGraph(adjacent_);
delete adjacent_;
adjacent_ = nullptr;
we_created_adjacent_ = false;
} else if (adjacent_) {
// If we adjusted an existing gap, unadjust here
adjacent_->set_length_and_media_out(adjacent_->length() - trim_diff);
}
if (block_->type() == Block::kClip) {
if (mode_ == Timeline::kTrimIn) {
static_cast<ClipBlock*>(block_)->waveform().PrependSilence(trim_diff);
} else {
static_cast<ClipBlock*>(block_)->waveform().AppendSilence(trim_diff);
}
}
} else {
if (adjacent_) {
// If trimmed LONGER, we adjusted an existing block
// (assume if there's no adjacent, we're at the end of the timeline and do nothing)
if (mode_ == Timeline::kTrimIn) {
adjacent_->set_length_and_media_out(adjacent_->length() - trim_diff);
}
}
if (block_->type() == Block::kClip) {
if (mode_ == Timeline::kTrimIn) {
static_cast<ClipBlock*>(block_)->waveform().TrimIn(-trim_diff);
} else {
static_cast<ClipBlock*>(block_)->waveform().TrimOut(-trim_diff);
}
}
}
TimeRange invalidate_range;
if (mode_ == Timeline::kTrimIn) {
block_->set_length_and_media_in(old_length_);
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff);
} else {
block_->set_length_and_media_out(old_length_);
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff);
}
track_->UnblockInvalidateCache();
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
TrackReplaceBlockWithGapCommand::TrackReplaceBlockWithGapCommand(TrackOutput *track, Block *block, QUndoCommand *command) :
UndoCommand(command),
track_(track),
block_(block),
we_created_gap_(false),
gap_(nullptr),
merged_gap_(nullptr)
{
}
Project *TrackReplaceBlockWithGapCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(block_->parent())->project();
}
void TrackReplaceBlockWithGapCommand::redo_internal()
{
TimeRange invalidate_range;
track_->BlockInvalidateCache();
// If the block has no next, it's at the end of the track and there's no need to create a gap
if (block_->next()) {
invalidate_range = TimeRange(block_->in(), block_->out());
rational new_gap_length = block_->length();
bool previous_is_a_gap = (block_->previous() && block_->previous()->type() == Block::kGap);
bool next_is_a_gap = (block_->next() && block_->next()->type() == Block::kGap);
if (previous_is_a_gap) {
// Extend gap before this block
gap_ = static_cast<GapBlock*>(block_->previous());
// If the next is also a gap, we'll merge the two
if (next_is_a_gap) {
merged_gap_ = static_cast<GapBlock*>(block_->next());
new_gap_length += merged_gap_->length();
track_->RippleRemoveBlock(merged_gap_);
TakeNodeFromParentGraph(merged_gap_, &memory_manager_);
}
} else if (next_is_a_gap) {
// Extend gap after this block
gap_ = static_cast<GapBlock*>(block_->next());
}
if (gap_) {
// Extend an existing gap
new_gap_length += gap_->length();
gap_->set_length_and_media_out(new_gap_length);
track_->RippleRemoveBlock(block_);
} else {
// No gap exists, create one
gap_ = new GapBlock();
gap_->set_length_and_media_out(new_gap_length);
static_cast<NodeGraph*>(track_->parent())->AddNode(gap_);
track_->ReplaceBlock(block_, gap_);
we_created_gap_ = true;
}
} else {
rational earliest_change = block_->in();
// Handle the gap being at the end where no gap is necessary
track_->RippleRemoveBlock(block_);
// If there were also gaps leading up to this block, clean them up here
if (!track_->Blocks().isEmpty() && track_->Blocks().last()->type() == Block::kGap) {
merged_gap_ = static_cast<GapBlock*>(track_->Blocks().last());
earliest_change = merged_gap_->in();
track_->RippleRemoveBlock(merged_gap_);
TakeNodeFromParentGraph(merged_gap_, &memory_manager_);
}
invalidate_range = TimeRange(earliest_change, RATIONAL_MAX);
}
track_->UnblockInvalidateCache();
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
void TrackReplaceBlockWithGapCommand::undo_internal()
{
TimeRange invalidate_range;
track_->BlockInvalidateCache();
if (gap_) {
if (we_created_gap_) {
// We made this gap, simply swap our gap back
track_->ReplaceBlock(gap_, block_);
TakeNodeFromParentGraph(gap_);
delete gap_;
gap_ = nullptr;
} else {
// We must have extended an existing gap
rational original_gap_length = gap_->length() - block_->length();
// If we merged two gaps together, restore it now
if (merged_gap_) {
original_gap_length -= merged_gap_->length();
static_cast<NodeGraph*>(track_->parent())->AddNode(merged_gap_);
track_->InsertBlockAfter(merged_gap_, gap_);
}
// Restore original block
track_->InsertBlockAfter(block_, gap_);
// Restore gap's original length
gap_->set_length_and_media_out(original_gap_length);
}
invalidate_range = TimeRange(block_->in(), block_->out());
} else {
// If there's no `gap_`, we must have removed the block at the end
invalidate_range = TimeRange(track_->length(), RATIONAL_MAX);
if (merged_gap_) {
static_cast<NodeGraph*>(track_->parent())->AddNode(merged_gap_);
track_->AppendBlock(merged_gap_);
}
track_->AppendBlock(block_);
}
merged_gap_ = nullptr;
track_->UnblockInvalidateCache();
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
TrackSlideCommand::TrackSlideCommand(const QVector<TrackSlideCommand::BlockSlideInfo> &blocks, QUndoCommand *parent) :
UndoCommand(parent),
blocks_(blocks)
{
}
Project *TrackSlideCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(blocks_.first().track->parent())->project();
}
void TrackSlideCommand::redo_internal()
{
slide_internal(false);
}
void TrackSlideCommand::undo_internal()
{
slide_internal(true);
}
void TrackSlideCommand::slide_internal(bool undo)
{
QMap<TrackOutput*, TimeRangeList> invalidate_ranges;
// Make sure all movement blocks' old positions are invalidated
foreach (const BlockSlideInfo& info, blocks_) {
if (info.mode == Timeline::kMove) {
invalidate_ranges[info.track].InsertTimeRange(TimeRange(info.block->in(),
info.block->out()));
}
}
// Perform trims
foreach (const BlockSlideInfo& info, blocks_) {
info.track->BlockInvalidateCache();
if (info.mode == Timeline::kTrimIn || info.mode == Timeline::kTrimOut) {
rational new_len = undo ? info.old_time : info.new_time;
if (info.block->type() == Block::kClip) {
AudioVisualWaveform& waveform = static_cast<ClipBlock*>(info.block)->waveform();
if (new_len < info.block->length()) {
if (info.mode == Timeline::kTrimIn) {
waveform.TrimIn(info.block->length() - new_len);
} else {
waveform.TrimOut(info.block->length() - new_len);
}
} else {
if (info.mode == Timeline::kTrimIn) {
waveform.PrependSilence(new_len - info.block->length());
} else {
waveform.AppendSilence(new_len - info.block->length());
}
}
}
if (info.mode == Timeline::kTrimIn) {
info.block->set_length_and_media_in(new_len);
} else {
info.block->set_length_and_media_out(new_len);
}
} else if (!undo && info.mode == Timeline::kMove && !info.block->previous()) {
// If this is a moving block and there was nothing before it to offset its time correctly,
// insert a gap here
GapBlock* gap = new GapBlock();
gap->set_length_and_media_out(info.new_time);
static_cast<NodeGraph*>(info.block->parent())->AddNode(gap);
info.track->PrependBlock(gap);
added_gaps_.append(gap);
}
info.track->UnblockInvalidateCache();
}
if (undo) {
// If undoing, remove added gaps
foreach (GapBlock* gap, added_gaps_) {
TrackOutput* track = TrackOutput::TrackFromBlock(gap);
track->BlockInvalidateCache();
track->RippleRemoveBlock(gap);
TakeNodeFromParentGraph(gap);
delete gap;
track->UnblockInvalidateCache();
}
added_gaps_.clear();
}
// Make sure all movement blocks' new positions are invalidated
foreach (const BlockSlideInfo& info, blocks_) {
if (info.mode == Timeline::kMove) {
invalidate_ranges[info.track].InsertTimeRange(TimeRange(info.block->in(),
info.block->out()));
}
}
QMap<TrackOutput*, TimeRangeList>::const_iterator i;
for (i=invalidate_ranges.constBegin(); i!=invalidate_ranges.constEnd(); i++) {
foreach (const TimeRange& r, i.value()) {
i.key()->InvalidateCache(r, i.key()->block_input(), i.key()->block_input());
}
}
}
OLIVE_NAMESPACE_EXIT