Skip to content

Commit f976794

Browse files
committed
Improve memory usage and performance for importing large assets
1 parent 2478bbb commit f976794

File tree

16 files changed

+305
-201
lines changed

16 files changed

+305
-201
lines changed

api/lib/src/helpers/import.dart

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
1+
import 'dart:typed_data';
2+
13
import 'package:butterfly_api/butterfly_api.dart';
24

5+
const kAssetScheme = 'asset';
6+
37
typedef ImportAssetOptions = (
48
NoteData data,
59
List<PadElement> elements, {
610
void Function(String source)? onInvalidate,
711
Map<String, String>? alreadyImported,
12+
Map<String, Uint8List>? assets,
813
});
914

1015
(NoteData, List<PadElement>) importAssets(ImportAssetOptions options) {
1116
final imported = options.alreadyImported ?? {};
1217
var data = options.$1;
1318
var elements = options.$2;
19+
String importBytes(
20+
SourcedElement element,
21+
String fileExtension,
22+
Uint8List bytes,
23+
) {
24+
(NoteData, String) result;
25+
if (element is PdfElement) {
26+
result = data.importPdf(bytes);
27+
} else {
28+
result = data.importImage(bytes, fileExtension);
29+
}
30+
data = result.$1;
31+
final path = Uri.file(result.$2, windows: false).toString();
32+
return imported[element.source] = path;
33+
}
34+
1435
String importAsset(SourcedElement element, String fileExtension) {
1536
final source = element.source;
1637
options.onInvalidate?.call(source);
@@ -20,22 +41,19 @@ typedef ImportAssetOptions = (
2041
final uri = Uri.tryParse(source);
2142
final uriData = uri?.data;
2243
if (uriData == null) {
23-
if ((uri?.isScheme('file') ?? false) || !(uri?.hasScheme ?? true)) {
24-
data = data.undoDelete(uri!.path);
44+
if ((uri?.isScheme('file') ?? false)) {
45+
imported[source] = uri!.toFilePath(windows: false);
46+
}
47+
if (uri?.isScheme(kAssetScheme) ?? false) {
48+
final bytes = options.assets?[source];
49+
if (bytes != null) {
50+
return importBytes(element, fileExtension, bytes);
51+
}
2552
}
2653
return source;
2754
}
2855
final bytes = uriData.contentAsBytes();
29-
(NoteData, String) result;
30-
if (element is PdfElement) {
31-
result = data.importPdf(bytes);
32-
} else {
33-
result = data.importImage(bytes, fileExtension);
34-
}
35-
data = result.$1;
36-
final path = Uri.file(result.$2, windows: false).toString();
37-
imported[source] = path;
38-
return path;
56+
return importBytes(element, fileExtension, bytes);
3957
}
4058

4159
elements = elements

api/lib/src/models/tool.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import 'dart:math';
2+
import 'dart:typed_data';
23

34
import 'package:butterfly_api/src/converter/color.dart';
45
import 'package:dart_leap/dart_leap.dart';
56
import 'package:freezed_annotation/freezed_annotation.dart';
67

8+
import '../converter/core.dart';
79
import 'area.dart';
810
import 'colors.dart';
911
import 'element.dart';
@@ -72,6 +74,7 @@ sealed class Tool with _$Tool {
7274
@Default('') String displayIcon,
7375
required List<PadElement> elements,
7476
required List<Area> areas,
77+
@Uint8ListJsonConverter() @Default({}) Map<String, Uint8List> assets,
7578
}) = ImportTool;
7679

7780
factory Tool.undo({

api/lib/src/models/tool.freezed.dart

Lines changed: 13 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/lib/src/models/tool.g.dart

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/lib/src/protocol/event.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,10 @@ sealed class DocumentEvent extends ReplayEvent with _$DocumentEvent {
5555
const factory DocumentEvent.utilitiesChanged(UtilitiesState state) =
5656
UtilitiesChanged;
5757

58-
const factory DocumentEvent.elementsCreated(List<PadElement> elements) =
59-
ElementsCreated;
58+
const factory DocumentEvent.elementsCreated(
59+
List<PadElement> elements, {
60+
@Uint8ListJsonConverter() @Default({}) Map<String, Uint8List> assets,
61+
}) = ElementsCreated;
6062

6163
const factory DocumentEvent.elementsChanged(
6264
Map<String, List<PadElement>> elements,

api/lib/src/protocol/event.freezed.dart

Lines changed: 15 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/lib/src/protocol/event.g.dart

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/lib/actions/paste.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class PasteAction extends Action<PasteIntent> {
2020
if (state is! DocumentLoadSuccess) return;
2121
final importService = intent.context.read<ImportService>();
2222
try {
23-
importService.importClipboard(state.data);
23+
importService.importClipboard(state.data).then((e) => e?.submit());
2424
} catch (_) {}
2525
}
2626
}

app/lib/bloc/document_bloc.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ typedef ImportAssetSyncOptions = (
3535
List<PadElement> elements, {
3636
SendPort? onInvalidate,
3737
Map<String, String>? alreadyImported,
38+
Map<String, Uint8List>? assets,
3839
});
3940
(NoteData, List<PadElement>) _importAssetsSync(ImportAssetSyncOptions options) {
4041
return importAssets((
@@ -44,6 +45,7 @@ typedef ImportAssetSyncOptions = (
4445
? (source) => options.onInvalidate!.send(source)
4546
: null,
4647
alreadyImported: options.alreadyImported,
48+
assets: options.assets,
4749
));
4850
}
4951

@@ -52,6 +54,7 @@ Future<(NoteData, List<PadElement>)> importAssetsAsync(
5254
List<PadElement> elements, {
5355
void Function(String source)? onInvalidate,
5456
Map<String, String>? alreadyImported,
57+
Map<String, Uint8List>? assets,
5558
}) {
5659
ReceivePort? port;
5760
if (onInvalidate != null) {
@@ -67,6 +70,7 @@ Future<(NoteData, List<PadElement>)> importAssetsAsync(
6770
elements,
6871
onInvalidate: port?.sendPort,
6972
alreadyImported: alreadyImported,
73+
assets: assets,
7074
));
7175
}
7276

@@ -215,6 +219,7 @@ class DocumentBloc extends ReplayBloc<DocumentEvent, DocumentState> {
215219
current.data,
216220
event.elements.map((e) => e.copyWith(id: createUniqueId())).toList(),
217221
onInvalidate: assetService.invalidate,
222+
assets: event.assets,
218223
);
219224
final renderers = elements
220225
.map((e) => Renderer.fromInstance(e, current.currentLayer))

app/lib/dialogs/elements.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ ContextMenuBuilder buildElementsContextMenu(
4242
onPressed: () {
4343
Navigator.of(context).pop(true);
4444
try {
45-
importService.importClipboard(
46-
state.data,
47-
position: state.transformCubit.state.localToGlobal(position),
48-
);
45+
importService
46+
.importClipboard(
47+
state.data,
48+
position: state.transformCubit.state.localToGlobal(
49+
position,
50+
),
51+
)
52+
.then((e) => e?.submit());
4953
} catch (_) {}
5054
},
5155
icon: const PhosphorIcon(PhosphorIconsLight.clipboard),

0 commit comments

Comments
 (0)