| // Copyright (c) 2022, 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:collection'; | |
| import 'package:compiler/src/serialization/serialization.dart'; | |
| /// Interface for data that may be deserialized lazily. | |
| /// | |
| /// This interface should be used to wrap data objects that aren't needed in | |
| /// later phases of the compiler. Usage of this class should follow a set | |
| /// pattern. Given a class `C` with a field `m0` of type `E` that we wish to | |
| /// make deferrable: | |
| /// | |
| /// 1) `m0` should be replaced with an internal field, `_m1`, of type | |
| /// `Deferrable<E>`. | |
| /// 2) An internal constructor should be added to `C` that takes a `d` of type | |
| /// `Deferrable<E>` to initialize `_m1`. This internal constructor should be | |
| /// called from the readFromSource method/factory to create the instance | |
| /// of `C`. `d` should be obtained using [DataSourceReader.readDeferrable]. | |
| /// Note: [DataSourceReader.readDeferrable] passes the correct | |
| /// [DataSourceReader] to the reader function so that the [DataSourceReader] | |
| /// does not have to be closed over. If necessary a static tear-off should be | |
| /// used as the argument so a closure isn't created for every read. | |
| /// 3) Any existing constructors of `C` should maintain the same signature | |
| /// and initialize `_m1` passing `m` to [Deferrable.eager] where m is the value | |
| /// previously used to initialize `m0`. | |
| /// 4) If there are external references to `m0` then `C`s interface should be | |
| /// maintained. A getter `m0` should be added: `E get m0 => _m1.loaded()` | |
| /// 5) If all references to `m0` were internal, they can simply be replaced | |
| /// with calls to `_m1.loaded()`. | |
| /// | |
| /// Example class before: | |
| /// | |
| /// ``` | |
| /// class Foo { | |
| /// final Bar bar; | |
| /// final String name; | |
| /// | |
| /// Foo(this.bar, this.name); | |
| /// | |
| /// factory Foo.readFromSource(DataSourceReader reader) { | |
| /// final bar = Bar.readFromSource(reader); | |
| /// final name = reader.readString(); | |
| /// return Foo(bar, name); | |
| /// } | |
| /// } | |
| /// ``` | |
| /// | |
| /// After: | |
| /// | |
| /// ``` | |
| /// class Foo { | |
| /// Bar get bar => _bar.loaded(); | |
| /// final Deferrable<Bar> _bar; | |
| /// | |
| /// String get name => _name.loaded(); | |
| /// final Deferrable<String> _name; | |
| /// | |
| /// Foo(Bar bar, String name) : | |
| /// _bar = Deferrable.eager(bar), _name = Deferrable.eager(name); | |
| /// Foo._deserialized(this._bar, this._name); | |
| /// | |
| /// static String readName(DataSourceReader reader) => reader.readString(); | |
| /// | |
| /// factory Foo.readFromSource(DataSourceReader reader) { | |
| /// final bar = reader.readDeferrable(Bar.readFromSource); | |
| /// final name = reader.readDeferrable(readName); | |
| /// return Foo._deserialized(bar, name); | |
| /// } | |
| /// } | |
| /// ``` | |
| abstract class Deferrable<E> { | |
| E loaded(); | |
| factory Deferrable.deferred( | |
| DataSourceReader reader, | |
| E Function(DataSourceReader source) f, | |
| int offset, { | |
| bool cacheData = true, | |
| }) => cacheData | |
| ? _DeferredCache(reader, f, offset) | |
| : _Deferred(reader, f, offset); | |
| static Deferrable<E> deferredWithArg<E, A>( | |
| DataSourceReader reader, | |
| E Function(DataSourceReader source, A arg) f, | |
| A arg, | |
| int offset, { | |
| bool cacheData = true, | |
| }) => cacheData | |
| ? _DeferredCacheWithArg(reader, f, arg, offset) | |
| : _DeferredWithArg(reader, f, arg, offset); | |
| const factory Deferrable.eager(E data) = _Eager; | |
| const Deferrable(); | |
| } | |
| class _Eager<E> extends Deferrable<E> { | |
| final E _data; | |
| @override | |
| E loaded() => _data; | |
| const _Eager(this._data); | |
| } | |
| class _DeferredWithArg<E, A> extends Deferrable<E> { | |
| final DataSourceReader _reader; | |
| final E Function(DataSourceReader source, A arg) _dataLoader; | |
| final A _arg; | |
| final int _dataOffset; | |
| @override | |
| E loaded() => | |
| _reader.readWithOffset(_dataOffset, () => _dataLoader(_reader, _arg)); | |
| _DeferredWithArg(this._reader, this._dataLoader, this._arg, this._dataOffset); | |
| } | |
| class _DeferredCacheWithArg<E, A> extends Deferrable<E> { | |
| final int _dataOffset; | |
| // Below fields are nullable so they can be cleared after loading. | |
| DataSourceReader? _reader; | |
| E Function(DataSourceReader source, A arg)? _dataLoader; | |
| A? _arg; | |
| late final E _data = _loadData(); | |
| @override | |
| E loaded() => _data; | |
| E _loadData() { | |
| final reader = _reader!; | |
| final dataLoader = _dataLoader!; | |
| final arg = _arg as A; | |
| _reader = null; | |
| _dataLoader = null; | |
| _arg = null; | |
| return reader.readWithOffset(_dataOffset, () => dataLoader(reader, arg)); | |
| } | |
| _DeferredCacheWithArg( | |
| this._reader, | |
| this._dataLoader, | |
| this._arg, | |
| this._dataOffset, | |
| ); | |
| } | |
| class _Deferred<E> extends Deferrable<E> { | |
| final DataSourceReader _reader; | |
| final E Function(DataSourceReader source) _dataLoader; | |
| final int _dataOffset; | |
| @override | |
| E loaded() => _reader.readWithOffset(_dataOffset, () => _dataLoader(_reader)); | |
| _Deferred(this._reader, this._dataLoader, this._dataOffset); | |
| } | |
| class _DeferredCache<E> extends Deferrable<E> { | |
| final int _dataOffset; | |
| // Below fields are nullable so they can be cleared after loading. | |
| DataSourceReader? _reader; | |
| E Function(DataSourceReader source)? _dataLoader; | |
| late final E _data = _loadData(); | |
| @override | |
| E loaded() => _data; | |
| E _loadData() { | |
| final reader = _reader!; | |
| final dataLoader = _dataLoader!; | |
| _reader = null; | |
| _dataLoader = null; | |
| return reader.readWithOffset(_dataOffset, () => dataLoader(reader)); | |
| } | |
| _DeferredCache(this._reader, this._dataLoader, this._dataOffset); | |
| } | |
| /// Implementation of [Map] in which each value of type [V] is internally | |
| /// [Deferrable]. | |
| /// | |
| /// This map should be used when values of type [V] are expensive to | |
| /// deserialize. This abstracts away the laziness allowing the deferred map to | |
| /// be used as if the values were not deferred. | |
| /// | |
| /// The provided map can have a mix of eager and lazy [Deferrable]s if | |
| /// there are a mix of expensive and cheap values. | |
| class DeferrableValueMap<K, V> with MapMixin<K, V> { | |
| DeferrableValueMap(this._map); | |
| final Map<K, Deferrable<V>> _map; | |
| @override | |
| V? operator [](Object? key) { | |
| return _map[key]?.loaded(); | |
| } | |
| @override | |
| void operator []=(K key, V value) { | |
| _map[key] = Deferrable.eager(value); | |
| } | |
| @override | |
| void clear() { | |
| _map.clear(); | |
| } | |
| @override | |
| Iterable<K> get keys => _map.keys; | |
| @override | |
| V? remove(Object? key) { | |
| return _map.remove(key)?.loaded(); | |
| } | |
| @override | |
| V putIfAbsent(K key, V Function() ifAbsent) { | |
| return _map.putIfAbsent(key, () => Deferrable.eager(ifAbsent())).loaded(); | |
| } | |
| } |