blob: 3f1303e96ae71750f2c1235a42716217a5ac11f9 [file] [log] [blame]
// 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:async';
import 'serialization.dart';
import 'serialization_extensions.dart';
/// Base class for types that need to be able to be traced back to a specific
/// instance on the server side.
abstract class RemoteInstance implements Serializable {
/// The unique ID for this instance.
final int id;
/// The type of instance being encoded.
RemoteInstanceKind get kind;
/// Static, incrementing ids.
static int _nextId = 0;
/// Gets the next unique identifier.
static int get uniqueId => _nextId++;
/// On the client side [id]s are given and you should reconstruct objects with
/// the given ID. On the server side ids should be created using
/// [RemoteInstance.uniqueId].
RemoteInstance(this.id);
/// Retrieves a cached instance by ID, if present.
static RemoteInstance? cached(int id) => _remoteInstanceCache[id];
/// Adds [instance] to the cache for this zone.
static void cache(RemoteInstance instance) =>
_remoteInstanceCache[instance.id] = instance;
/// Deserializes an instance based on the current [serializationMode].
///
// TODO: Ideally this would be `T extends RemoteInstance` but that interacts
// poorly with inference in other places, we end up with Never and null as
// inferred types due to only the impl versions of objects extending
// `RemoteInstance`.
static T deserialize<T extends Object>(Deserializer deserializer) =>
(deserializer..moveNext()).expectRemoteInstance();
/// This method should be overridden by any subclasses, they should instead
/// implement [serializeUncached].
@override
void serialize(Serializer serializer) {
serializer.addInt(id);
// We only send the ID if it's in the cache, it's only in our cache if it is
// also in the remote cache.
if (_remoteInstanceCache.containsKey(id)) return;
serializeUncached(serializer);
}
/// This method should be overridden by all subclasses, which should on their
/// first line call this super method.
///
/// This method should not be directly invoked, instead only [serialize]
/// should call it (if serializing an uncached value).
///
/// Only new fields added by the subtype should be serialized here, rely on
/// super classes to have their own implementations for their fields.
void serializeUncached(Serializer serializer) {
serializer.addInt(kind.index);
// Now we can add it to the cache, we know the other side has a copy of it
// and don't need to serialize it in the future.
_remoteInstanceCache[id] = this;
}
@override
bool operator ==(Object other) => other is RemoteInstance && id == other.id;
@override
int get hashCode => id;
}
/// A remote instance which is just a pointer to some server side instance of
/// a generic object.
///
/// The wrapped object is not serialized.
final class RemoteInstanceImpl extends RemoteInstance {
/// Always null on the client side, has an actual instance on the server side.
final Object? instance;
@override
final RemoteInstanceKind kind;
RemoteInstanceImpl({
required int id,
this.instance,
required this.kind,
}) : super(id);
}
// The kinds of instances.
enum RemoteInstanceKind {
classDeclaration,
constructorDeclaration,
constructorMetadataAnnotation,
declarationPhaseIntrospector,
definitionPhaseIntrospector,
enumDeclaration,
enumValueDeclaration,
extensionDeclaration,
extensionTypeDeclaration,
fieldDeclaration,
formalParameter,
formalParameterDeclaration,
functionDeclaration,
functionTypeAnnotation,
identifier,
identifierMetadataAnnotation,
library,
methodDeclaration,
mixinDeclaration,
namedStaticType,
namedTypeAnnotation,
omittedTypeAnnotation,
recordField,
recordTypeAnnotation,
staticType,
typeAliasDeclaration,
typeParameter,
typeParameterDeclaration,
typePhaseIntrospector,
variableDeclaration,
// Exceptions.
macroImplementationException,
macroIntrospectionCycleException,
unexpectedMacroException,
}
/// Creates a new zone with a remote instance cache and an id, which it uses to
/// avoid sending the same remote instances across the wire multiple times.
///
/// The lifecycle of one of these zones should be no longer than that of a
/// single full compile, at which point [destroyRemoteInstanceZone] should be
/// called.
///
/// In order to keep these caches in sync between the server and client, the
/// server always creates new zone IDs and passes those to the client.
int newRemoteInstanceZone<T>() {
final int id = _nextSerializationZoneId++;
final Zone zone = Zone.current.fork(zoneValues: {
_remoteInstanceZoneCacheKey: <int, RemoteInstance>{},
});
_remoteInstanceCacheZones[id] = zone;
return id;
}
/// Runs [fn] in the remote instance zone identified by [zoneId].
///
/// If [createIfMissing] is `true`, then a new zone will be created with
/// [zoneId] if one does not already exist (this should only be `true` in client
/// code).
T withRemoteInstanceZone<T>(int zoneId, T Function() fn,
{bool createIfMissing = false}) {
Zone? zone = _remoteInstanceCacheZones[zoneId];
if (zone == null) {
if (!createIfMissing) {
throw StateError('No remote instance zone with id `$zoneId` exists.');
}
zone = _remoteInstanceCacheZones[zoneId] = Zone.current.fork(zoneValues: {
_remoteInstanceZoneCacheKey: <int, RemoteInstance>{},
});
}
return zone.run(fn);
}
/// Removes the remote instance zone identified by [zoneId] from the known list
/// of zones and forcibly clears its cache.
///
/// Throws if a zone identified by [zoneId] does not exist.
void destroyRemoteInstanceZone(int zoneId) {
final Zone? zone = _remoteInstanceCacheZones.remove(zoneId);
if (zone == null) {
throw StateError('No remote instance zone with id `$zoneId` exists.');
}
(zone[_remoteInstanceZoneCacheKey] as Map<int, RemoteInstance>).clear();
}
/// The key used to store the remote instance cache in the current zone.
const Symbol _remoteInstanceZoneCacheKey = #_remoteInstanceCache;
/// We cache remote instances by their ID, which allows us to not repeatedly
/// send the same information over the wire.
///
/// These are a part of the current remote instance cache zone, which all
/// serialization and deserialization of remote instances must be done in.
Map<int, RemoteInstance> get _remoteInstanceCache =>
Zone.current[_remoteInstanceZoneCacheKey] as Map<int, RemoteInstance>? ??
(throw StateError('Not running in a remote instance cache zone, call '
'`withRemoteInstanceZone` to set one up.'));
/// Remote instance cache zones by ID.
final _remoteInstanceCacheZones = <int, Zone>{};
/// Incrementing identifier for the serialization zone ids.
int _nextSerializationZoneId = 0;