Skip to content

Commit 81356ce

Browse files
committed
Add write caching.
1 parent 396b157 commit 81356ce

File tree

7 files changed

+120
-272
lines changed

7 files changed

+120
-272
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
}

build/lib/src/state/filesystem_cache.dart

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ abstract interface class FilesystemCache {
1818
/// Waits for any pending reads to complete first.
1919
Future<void> invalidate(Iterable<AssetId> ids);
2020

21+
Future<void> flush();
22+
2123
/// Whether [id] exists.
2224
///
2325
/// Returns a cached result if available, or caches and returns `ifAbsent()`.
@@ -31,6 +33,12 @@ abstract interface class FilesystemCache {
3133
required Future<Uint8List> Function() ifAbsent,
3234
});
3335

36+
Future<void> writeAsBytes(
37+
AssetId id,
38+
List<int> contents, {
39+
required Future<void> Function() writer,
40+
});
41+
3442
/// Reads [id] as a `String`.
3543
///
3644
/// Returns a cached result if available, or caches and returns `ifAbsent()`.
@@ -39,6 +47,15 @@ abstract interface class FilesystemCache {
3947
Encoding encoding = utf8,
4048
required Future<Uint8List> Function() ifAbsent,
4149
});
50+
51+
Future<void> writeAsString(
52+
AssetId id,
53+
String contents, {
54+
Encoding encoding = utf8,
55+
required Future<void> Function() writer,
56+
});
57+
58+
Future<void> delete(AssetId id, {required Future<void> Function() deleter});
4259
}
4360

4461
/// [FilesystemCache] that always reads from the underlying source.
@@ -48,6 +65,9 @@ class PassthroughFilesystemCache implements FilesystemCache {
4865
@override
4966
Future<void> invalidate(Iterable<AssetId> ids) async {}
5067

68+
@override
69+
Future<void> flush() => Future<void>.value();
70+
5171
@override
5272
Future<bool> exists(
5373
AssetId id, {
@@ -66,6 +86,25 @@ class PassthroughFilesystemCache implements FilesystemCache {
6686
Encoding encoding = utf8,
6787
required Future<Uint8List> Function() ifAbsent,
6888
}) async => encoding.decode(await ifAbsent());
89+
90+
@override
91+
Future<void> writeAsBytes(
92+
AssetId id,
93+
List<int> contents, {
94+
required Future<void> Function() writer,
95+
}) => writer();
96+
97+
@override
98+
Future<void> writeAsString(
99+
AssetId id,
100+
String contents, {
101+
Encoding encoding = utf8,
102+
required Future<void> Function() writer,
103+
}) => writer();
104+
105+
@override
106+
Future<void> delete(AssetId id, {required Future<void> Function() deleter}) =>
107+
deleter();
69108
}
70109

71110
/// [FilesystemCache] that stores data in memory.
@@ -99,6 +138,8 @@ class InMemoryFilesystemCache implements FilesystemCache {
99138
/// Pending `readAsString` operations.
100139
final _pendingStringContentCache = <AssetId, Future<String>>{};
101140

141+
final _pendingWrites = <AssetId, Future<void> Function()>{};
142+
102143
@override
103144
Future<void> invalidate(Iterable<AssetId> ids) async {
104145
// First finish all pending operations, as they will write to the cache.
@@ -113,6 +154,14 @@ class InMemoryFilesystemCache implements FilesystemCache {
113154
}
114155
}
115156

157+
@override
158+
Future<void> flush() async {
159+
for (final write in _pendingWrites.values) {
160+
await write();
161+
}
162+
_pendingWrites.clear();
163+
}
164+
116165
@override
117166
Future<bool> exists(
118167
AssetId id, {
@@ -127,6 +176,11 @@ class InMemoryFilesystemCache implements FilesystemCache {
127176
var cached = _bytesContentCache[id];
128177
if (cached != null) return Future.value(cached);
129178

179+
// TODO(davidmorgan): ensure these can't be evicted.
180+
if (_pendingWrites.containsKey(id)) {
181+
throw StateError('Whoops, should have been cached: $id');
182+
}
183+
130184
return _pendingBytesContentCache.putIfAbsent(id, () async {
131185
final result = await ifAbsent();
132186
_bytesContentCache[id] = result;
@@ -149,11 +203,54 @@ class InMemoryFilesystemCache implements FilesystemCache {
149203
var cached = _stringContentCache[id];
150204
if (cached != null) return cached;
151205

206+
// TODO(davidmorgan): ensure these can't be evicted.
207+
if (_pendingWrites.containsKey(id)) {
208+
throw StateError('Whoops, should have been cached: $id');
209+
}
210+
152211
return _pendingStringContentCache.putIfAbsent(id, () async {
153212
final bytes = await ifAbsent();
154213
final result = _stringContentCache[id] = utf8.decode(bytes);
155214
unawaited(_pendingStringContentCache.remove(id));
156215
return result;
157216
});
158217
}
218+
219+
@override
220+
Future<void> writeAsBytes(
221+
AssetId id,
222+
List<int> contents, {
223+
required Future<void> Function() writer,
224+
}) {
225+
_bytesContentCache[id] =
226+
contents is Uint8List ? contents : Uint8List.fromList(contents);
227+
_stringContentCache[id] = utf8.decode(contents);
228+
_canReadCache[id] = Future.value(true);
229+
230+
_pendingWrites[id] = writer;
231+
return Future.value(null);
232+
}
233+
234+
@override
235+
Future<void> writeAsString(
236+
AssetId id,
237+
String contents, {
238+
Encoding encoding = utf8,
239+
required Future<void> Function() writer,
240+
}) {
241+
// TODO: encoding?
242+
_stringContentCache[id] = contents;
243+
_bytesContentCache[id] = utf8.encode(contents);
244+
_canReadCache[id] = Future.value(true);
245+
246+
_pendingWrites[id] = writer;
247+
return Future.value(null);
248+
}
249+
250+
@override
251+
Future<void> delete(AssetId id, {required Future<void> Function() deleter}) {
252+
_pendingWrites[id] = deleter;
253+
_canReadCache[id] = Future.value(false);
254+
return Future.value(null);
255+
}
159256
}

build_runner_core/lib/src/asset/batch.dart

Lines changed: 0 additions & 160 deletions
This file was deleted.

build_runner_core/lib/src/asset/reader_writer.dart

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,13 @@ class ReaderWriter extends AssetReader
130130
@override
131131
Future<void> writeAsBytes(AssetId id, List<int> bytes) async {
132132
final path = _pathFor(id);
133-
await filesystem.writeAsBytes(path, bytes);
133+
await cache.writeAsBytes(
134+
id,
135+
bytes,
136+
writer: () {
137+
return filesystem.writeAsBytes(path, bytes);
138+
},
139+
);
134140
}
135141

136142
@override
@@ -140,7 +146,14 @@ class ReaderWriter extends AssetReader
140146
Encoding encoding = utf8,
141147
}) async {
142148
final path = _pathFor(id);
143-
await filesystem.writeAsString(path, contents, encoding: encoding);
149+
await cache.writeAsString(
150+
id,
151+
contents,
152+
encoding: encoding,
153+
writer: () {
154+
return filesystem.writeAsString(path, contents, encoding: encoding);
155+
},
156+
);
144157
}
145158

146159
@override
@@ -160,19 +173,19 @@ class ReaderWriter extends AssetReader
160173
'Should not delete assets outside of $rootPackage',
161174
);
162175
}
163-
await filesystem.delete(path);
176+
await cache.delete(
177+
id,
178+
deleter: () {
179+
return filesystem.delete(path);
180+
},
181+
);
164182
}
165183

166184
@override
167185
Future<void> deleteDirectory(AssetId id) async {
168186
final path = _pathFor(id);
169187
await filesystem.deleteDirectory(path);
170188
}
171-
172-
@override
173-
Future<void> completeBuild() async {
174-
// TODO(davidmorgan): add back write caching, "batching".
175-
}
176189
}
177190

178191
/// [AssetFinder] that uses [PackageGraph] to map packages to paths, then

0 commit comments

Comments
 (0)