-
Notifications
You must be signed in to change notification settings - Fork 212
/
Copy pathmodule_cache.dart
114 lines (98 loc) · 3.7 KB
/
module_cache.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'package:async/async.dart';
import 'package:build/build.dart';
import 'package:crypto/crypto.dart';
import 'meta_module.dart';
import 'modules.dart';
Map<String, dynamic> _deserialize(List<int> bytes) =>
jsonDecode(utf8.decode(bytes)) as Map<String, dynamic>;
List<int> _serialize(Map<String, dynamic> data) =>
utf8.encode(jsonEncode(data));
final metaModuleCache = DecodingCache.resource(
(m) => MetaModule.fromJson(_deserialize(m)),
(m) => _serialize(m.toJson()),
);
final moduleCache = DecodingCache.resource(
(m) => Module.fromJson(_deserialize(m)),
(m) => _serialize(m.toJson()),
);
/// A cache of objects decoded from written assets suitable for use as a
/// [Resource].
///
/// Instances that are decoded will be cached throughout the duration of a build
/// and invalidated between builds. Instances that are shared through the cache
/// should be treated as immutable to avoid leaking any information which was
/// not loaded from the underlying asset.
class DecodingCache<T> {
/// Create a [Resource] which can decoded instances of [T] serialized via json
/// to assets.
static Resource<DecodingCache<T>> resource<T>(
T Function(List<int>) fromBytes,
List<int> Function(T) toBytes,
) => Resource<DecodingCache<T>>(
() => DecodingCache._(fromBytes, toBytes),
dispose: (c) => c._dispose(),
);
final _cached = <AssetId, _Entry<T>>{};
final T Function(List<int>) _fromBytes;
final List<int> Function(T) _toBytes;
DecodingCache._(this._fromBytes, this._toBytes);
void _dispose() {
_cached.removeWhere((_, entry) => entry.digest == null);
for (var entry in _cached.values) {
entry.needsCheck = true;
}
}
/// Find and deserialize a [T] stored in [id].
///
/// If the asset at [id] is unreadable the returned future will resolve to
/// `null`. If the instance is cached it will not be decoded again, but the
/// content dependencies will be tracked through [reader].
Future<T?> find(AssetId id, AssetReader reader) async {
if (!await reader.canRead(id)) return null;
_Entry<T> entry;
if (!_cached.containsKey(id)) {
entry =
_cached[id] = _Entry(
Result.capture(reader.readAsBytes(id).then(_fromBytes)),
digest: Result.capture(reader.digest(id)),
);
} else {
entry = _cached[id]!;
if (entry.needsCheck) {
await (entry.onGoingCheck ??= () async {
var previousDigest =
entry.digest == null ? null : await Result.release(entry.digest!);
entry.digest = Result.capture(reader.digest(id));
if (await Result.release(entry.digest!) != previousDigest) {
entry.value = Result.capture(
reader.readAsBytes(id).then(_fromBytes),
);
}
entry
..needsCheck = false
..onGoingCheck = null;
}());
}
}
return Result.release(entry.value);
}
/// Serialized and write a [T] to [id].
///
/// The instance will be cached so that later calls to [find] may return the
/// instances without deserializing it.
Future<void> write(AssetId id, AssetWriter writer, T instance) async {
await writer.writeAsBytes(id, _toBytes(instance));
_cached[id] = _Entry(Result.capture(Future.value(instance)));
}
}
class _Entry<T> {
bool needsCheck = false;
Future<Result<T>> value;
Future<Result<Digest>>? digest;
Future<void>? onGoingCheck;
_Entry(this.value, {this.needsCheck = false, this.digest, this.onGoingCheck});
}