Skip to content

Commit cf45807

Browse files
committed
Add back write caching.
1 parent c0f78f3 commit cf45807

File tree

18 files changed

+468
-311
lines changed

18 files changed

+468
-311
lines changed

_test_common/lib/runner_asset_writer_spy.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,4 @@ class RunnerAssetWriterSpy extends AssetWriterSpy implements RunnerAssetWriter {
2323

2424
@override
2525
Future<void> deleteDirectory(AssetId id) => _delegate.deleteDirectory(id);
26-
27-
@override
28-
Future<void> completeBuild() async {}
2926
}

_test_common/lib/test_environment.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,12 @@ class TestBuildEnvironment implements BuildEnvironment {
7070
BuildEnvironment copyWith({
7171
void Function(LogRecord)? onLogOverride,
7272
RunnerAssetWriter? writer,
73+
AssetReader? reader,
7374
}) => TestBuildEnvironment(
74-
readerWriter: (writer as TestReaderWriter?) ?? _readerWriter,
75+
readerWriter:
76+
(writer as TestReaderWriter?) ??
77+
(reader as TestReaderWriter?) ??
78+
_readerWriter,
7579
throwOnPrompt: throwOnPrompt,
7680
);
7781

build/lib/src/state/filesystem_cache.dart

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66
import 'dart:convert';
77
import 'dart:typed_data';
88

9+
import '../../build.dart';
910
import '../asset/id.dart';
1011
import 'lru_cache.dart';
1112

@@ -16,6 +17,9 @@ abstract interface class FilesystemCache {
1617
/// Clears all [ids] from all caches.
1718
void invalidate(Iterable<AssetId> ids);
1819

20+
/// Flushes pending writes and deletes.
21+
void flush();
22+
1923
/// Whether [id] exists.
2024
///
2125
/// Returns a cached result if available, or caches and returns `ifAbsent()`.
@@ -26,6 +30,17 @@ abstract interface class FilesystemCache {
2630
/// Returns a cached result if available, or caches and returns `ifAbsent()`.
2731
Uint8List readAsBytes(AssetId id, {required Uint8List Function() ifAbsent});
2832

33+
/// Writes [contents] to [id].
34+
///
35+
/// [writer] is a function that does the actual write. If this cache does
36+
/// write caching, it is not called until [flush], and might not be called at
37+
/// all if another write to the same asset happens first.
38+
void writeAsBytes(
39+
AssetId id,
40+
List<int> contents, {
41+
required void Function() writer,
42+
});
43+
2944
/// Reads [id] as a `String`.
3045
///
3146
/// Returns a cached result if available, or caches and returns `ifAbsent()`.
@@ -34,6 +49,25 @@ abstract interface class FilesystemCache {
3449
Encoding encoding = utf8,
3550
required Uint8List Function() ifAbsent,
3651
});
52+
53+
/// Writes [contents] to [id].
54+
///
55+
/// [writer] is a function that does the actual write. If this cache does
56+
/// write caching, it is not called until [flush], and might not be called at
57+
/// all if another write to the same asset happens first.
58+
void writeAsString(
59+
AssetId id,
60+
String contents, {
61+
Encoding encoding = utf8,
62+
required void Function() writer,
63+
});
64+
65+
/// Deletes [id].
66+
///
67+
/// [deleter] is a function that does the actual write. If this cache does
68+
/// write caching, it is not called until [flush], and might not be called at
69+
/// all if another write to the same asset happens first.
70+
void delete(AssetId id, {required void Function() deleter});
3771
}
3872

3973
/// [FilesystemCache] that always reads from the underlying source.
@@ -43,19 +77,40 @@ class PassthroughFilesystemCache implements FilesystemCache {
4377
@override
4478
Future<void> invalidate(Iterable<AssetId> ids) async {}
4579

80+
@override
81+
void flush() {}
82+
4683
@override
4784
bool exists(AssetId id, {required bool Function() ifAbsent}) => ifAbsent();
4885

4986
@override
5087
Uint8List readAsBytes(AssetId id, {required Uint8List Function() ifAbsent}) =>
5188
ifAbsent();
5289

90+
@override
91+
void writeAsBytes(
92+
AssetId id,
93+
List<int> contents, {
94+
required void Function() writer,
95+
}) => writer();
96+
5397
@override
5498
String readAsString(
5599
AssetId id, {
56100
Encoding encoding = utf8,
57101
required Uint8List Function() ifAbsent,
58102
}) => encoding.decode(ifAbsent());
103+
104+
@override
105+
void writeAsString(
106+
AssetId id,
107+
String contents, {
108+
Encoding encoding = utf8,
109+
required void Function() writer,
110+
}) => writer();
111+
112+
@override
113+
void delete(AssetId id, {required void Function() deleter}) => deleter();
59114
}
60115

61116
/// [FilesystemCache] that stores data in memory.
@@ -83,8 +138,13 @@ class InMemoryFilesystemCache implements FilesystemCache {
83138
(value) => value.length,
84139
);
85140

141+
final _pendingWrites = <AssetId, _PendingWrite>{};
142+
86143
@override
87144
Future<void> invalidate(Iterable<AssetId> ids) async {
145+
if (_pendingWrites.isNotEmpty) {
146+
throw StateError("Can't invalidate while there are pending writes.");
147+
}
88148
for (var id in ids) {
89149
_existsCache.remove(id);
90150
_bytesContentCache.remove(id);
@@ -93,11 +153,30 @@ class InMemoryFilesystemCache implements FilesystemCache {
93153
}
94154

95155
@override
96-
bool exists(AssetId id, {required bool Function() ifAbsent}) =>
97-
_existsCache.putIfAbsent(id, ifAbsent);
156+
void flush() {
157+
for (final write in _pendingWrites.values) {
158+
write.writer();
159+
}
160+
_pendingWrites.clear();
161+
}
162+
163+
@override
164+
bool exists(AssetId id, {required bool Function() ifAbsent}) {
165+
final maybePendingWrite = _pendingWrites[id];
166+
if (maybePendingWrite != null) {
167+
return !maybePendingWrite.isDelete;
168+
}
169+
return _existsCache.putIfAbsent(id, ifAbsent);
170+
}
98171

99172
@override
100173
Uint8List readAsBytes(AssetId id, {required Uint8List Function() ifAbsent}) {
174+
final maybePendingWrite = _pendingWrites[id];
175+
if (maybePendingWrite != null) {
176+
// Throws if it's a delete; callers should check [exists] before reading.
177+
return maybePendingWrite.bytes!;
178+
}
179+
101180
final maybeResult = _bytesContentCache[id];
102181
if (maybeResult != null) return maybeResult;
103182

@@ -106,6 +185,24 @@ class InMemoryFilesystemCache implements FilesystemCache {
106185
return result;
107186
}
108187

188+
@override
189+
@override
190+
void writeAsBytes(
191+
AssetId id,
192+
List<int> contents, {
193+
required void Function() writer,
194+
}) {
195+
_stringContentCache.remove(id);
196+
final uint8ListContents =
197+
contents is Uint8List ? contents : Uint8List.fromList(contents);
198+
_bytesContentCache[id] = uint8ListContents;
199+
_existsCache[id] = true;
200+
_pendingWrites[id] = _PendingWrite(
201+
writer: writer,
202+
bytes: uint8ListContents,
203+
);
204+
}
205+
109206
@override
110207
String readAsString(
111208
AssetId id, {
@@ -117,9 +214,19 @@ class InMemoryFilesystemCache implements FilesystemCache {
117214
return encoding.decode(bytes);
118215
}
119216

217+
// Check _stringContentCache first to use it as a cache for conversion of
218+
// bytes from _pendingWrites.
120219
final maybeResult = _stringContentCache[id];
121220
if (maybeResult != null) return maybeResult;
122221

222+
final maybePendingWrite = _pendingWrites[id];
223+
if (maybePendingWrite != null) {
224+
// Throws if it's a delete; callers should check [exists] before reading.
225+
final converted = utf8.decode(maybePendingWrite.bytes!);
226+
_stringContentCache[id] = converted;
227+
return converted;
228+
}
229+
123230
var bytes = _bytesContentCache[id];
124231
if (bytes == null) {
125232
bytes = ifAbsent();
@@ -129,4 +236,40 @@ class InMemoryFilesystemCache implements FilesystemCache {
129236
_stringContentCache[id] = result;
130237
return result;
131238
}
239+
240+
@override
241+
void writeAsString(
242+
AssetId id,
243+
String contents, {
244+
Encoding encoding = utf8,
245+
required void Function() writer,
246+
}) {
247+
_stringContentCache[id] = contents;
248+
_bytesContentCache.remove(id);
249+
_existsCache[id] = true;
250+
251+
final encoded = encoding.encode(contents);
252+
_pendingWrites[id] = _PendingWrite(
253+
writer: writer,
254+
bytes: encoded is Uint8List ? encoded : Uint8List.fromList(encoded),
255+
);
256+
}
257+
258+
@override
259+
void delete(AssetId id, {required void Function() deleter}) {
260+
_stringContentCache.remove(id);
261+
_bytesContentCache.remove(id);
262+
_existsCache[id] = false;
263+
_pendingWrites[id] = _PendingWrite(writer: deleter);
264+
}
265+
}
266+
267+
/// The data that will be written on flush; used for reads before flush.
268+
class _PendingWrite {
269+
final void Function() writer;
270+
final Uint8List? bytes;
271+
272+
_PendingWrite({required this.writer, this.bytes});
273+
274+
bool get isDelete => bytes == null;
132275
}

0 commit comments

Comments
 (0)