Aegisub/aegisub/src/subs_grid.cpp
Thomas Goyne d5aae26d83 Use boost::flyweight to intern the wxString members of AssDialogue
100 no-op non-amend commits on a subtitle file with 6689 dialogue lines,
with the undo limit set to 100:

Without flyweight:
	No video open:
		Initial memory usage: 30.6 MB
		Final memory usage: 498.0 MB
		Elapsed time: 6.3 seconds
	Video open, using libass:
		Initial memory usage: 54.3 MB
		Final memory usage: 653.3 MB
		Elapsed time: 23.7 seconds

With flyweight:
	No video open:
		Initial memory usage: 26.0 MB
		Final memory usage: 104.5 MB
		Elapsed time: 3.0 seconds
	Video open, using libass:
		Initial memory usage: 46.7 MB
		Final memory usage: 251.8 MB
		Elapsed time: 13.0 seconds

No video open:
	Memory usage: -79%
	Time: -52%
Video open:
	Memory usage: -61.5%
	Time: -45%

100 no-op amend commits on a line in the middle of a subtitle file with
6689 dialogue lines, with video open:

Without flyweight:
	Initial memory usage: 48.2 MB
	Final memory usage: 182.3 MB
	Elapsed time: 22.3 seconds

With flyweight:
	Initial memory usage: 39.8 MB
	Final memory usage: 165.8 MB
	Elapsed time: 13.8 seconds

Note: The large jump in memory usage here is due to that the benchmark
is blocking the main thread, so at the end there are ~100 video frames
waiting to be displayed.
2012-12-05 18:43:44 -08:00

198 lines
5.4 KiB
C++

// Copyright (c) 2006, Rodrigo Braz Monteiro
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
/// @file subs_grid.cpp
/// @brief Subtitles grid control in main window
/// @ingroup main_ui
///
#include "config.h"
#include <algorithm>
#include <utility>
#include <wx/clipbrd.h>
#include <wx/filename.h>
#include <wx/regex.h>
#include <wx/tokenzr.h>
#include "subs_grid.h"
#include "include/aegisub/context.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "main.h"
#include "utils.h"
#include "video_context.h"
SubtitlesGrid::SubtitlesGrid(wxWindow *parent, agi::Context *context)
: BaseGrid(parent, context, wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER)
{
}
static void trim_text(wxString *text) {
static wxRegEx start("^( |\t|\\\\[nNh])+");
static wxRegEx end("( |\t|\\\\[nNh])+$");
start.ReplaceFirst(text, "");
end.ReplaceFirst(text, "");
}
static void expand_times(AssDialogue *src, AssDialogue *dst) {
dst->Start = std::min(dst->Start, src->Start);
dst->End = std::max(dst->End, src->End);
}
static bool check_lines(AssDialogue *d1, AssDialogue *d2, bool (wxString::*pred)(wxString const&, wxString *) const) {
wxString rest;
if ((d1->Text.get().*pred)(d2->Text.get(), &rest)) {
trim_text(&rest);
d1->Text = rest;
expand_times(d1, d2);
return true;
}
return false;
}
static bool check_start(AssDialogue *d1, AssDialogue *d2) {
return check_lines(d1, d2, &wxString::StartsWith);
}
static bool check_end(AssDialogue *d1, AssDialogue *d2) {
return check_lines(d1, d2, &wxString::EndsWith);
}
/// @brief Recombine
void SubtitlesGrid::RecombineLines() {
Selection selectedSet = GetSelectedSet();
if (selectedSet.size() < 2) return;
AssDialogue *activeLine = GetActiveLine();
std::vector<AssDialogue*> sel(selectedSet.begin(), selectedSet.end());
sort(sel.begin(), sel.end(), &AssFile::CompStart);
for (auto &diag : sel) {
wxString text = diag->Text;
trim_text(&text);
diag->Text = text;
}
auto end = sel.end() - 1;
for (auto cur = sel.begin(); cur != end; ++cur) {
AssDialogue *d1 = *cur;
auto d2 = cur + 1;
// 1, 1+2 (or 2+1), 2 gets turned into 1, 2, 2 so kill the duplicate
if (d1->Text == (*d2)->Text) {
expand_times(d1, *d2);
delete d1;
continue;
}
// 1, 1+2, 1 turns into 1, 2, [empty]
if (d1->Text.get().empty()) {
delete d1;
continue;
}
// If d2 is the last line in the selection it'll never hit the above test
if (d2 == end && (*d2)->Text.get().empty()) {
delete *d2;
continue;
}
// 1, 1+2
while (d2 <= end && check_start(*d2, d1))
++d2;
// 1, 2+1
while (d2 <= end && check_end(*d2, d1))
++d2;
// 1+2, 2
while (d2 <= end && check_end(d1, *d2))
++d2;
// 2+1, 2
while (d2 <= end && check_start(d1, *d2))
++d2;
}
// Remove now non-existent lines from the selection
Selection lines;
transform(context->ass->Line.begin(), context->ass->Line.end(), inserter(lines, lines.begin()), cast<AssDialogue*>());
Selection newSel;
set_intersection(lines.begin(), lines.end(), selectedSet.begin(), selectedSet.end(), inserter(newSel, newSel.begin()));
if (newSel.empty())
newSel.insert(*lines.begin());
// Restore selection
if (!newSel.count(activeLine))
activeLine = *newSel.begin();
SetSelectionAndActive(newSel, activeLine);
context->ass->Commit(_("combining"), AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL);
}
void SubtitlesGrid::AdjoinLines(int n1,int n2,bool setStart) {
if (n1 == n2) {
if (setStart) {
--n1;
}
else {
++n2;
}
}
// Set start
if (setStart) {
AssDialogue *prev = GetDialogue(n1);
AssDialogue *cur;
for (int i=n1+1;i<=n2;i++) {
cur = GetDialogue(i);
if (!cur) return;
cur->Start = prev->End;
prev = cur;
}
}
// Set end
else {
AssDialogue *next;
AssDialogue *cur = GetDialogue(n1);
for (int i=n1;i<n2;i++) {
next = GetDialogue(i+1);
if (!next) return;
cur->End = next->Start;
cur = next;
}
}
context->ass->Commit(_("adjoin"), AssFile::COMMIT_DIAG_TIME);
}