blob: 48c5859dc3d5b4e8f030f702f98f198e8dc9c0df [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../generator_tools.dart';
/// Creates the `InstanceManager` with the passed string values.
String instanceManagerTemplate({
required Iterable<String> allProxyApiNames,
}) {
final Iterable<String> apiHandlerSetUps = allProxyApiNames.map(
(String name) {
return '$name.${classMemberNamePrefix}setUpMessageHandlers(${classMemberNamePrefix}instanceManager: instanceManager);';
},
);
return '''
/// Maintains instances used to communicate with the native objects they
/// represent.
///
/// Added instances are stored as weak references and their copies are stored
/// as strong references to maintain access to their variables and callback
/// methods. Both are stored with the same identifier.
///
/// When a weak referenced instance becomes inaccessible,
/// [onWeakReferenceRemoved] is called with its associated identifier.
///
/// If an instance is retrieved and has the possibility to be used,
/// (e.g. calling [getInstanceWithWeakReference]) a copy of the strong reference
/// is added as a weak reference with the same identifier. This prevents a
/// scenario where the weak referenced instance was released and then later
/// returned by the host platform.
class $instanceManagerClassName {
/// Constructs a [$instanceManagerClassName].
$instanceManagerClassName({required void Function(int) onWeakReferenceRemoved}) {
this.onWeakReferenceRemoved = (int identifier) {
_weakInstances.remove(identifier);
onWeakReferenceRemoved(identifier);
};
_finalizer = Finalizer<int>(this.onWeakReferenceRemoved);
}
// Identifiers are locked to a specific range to avoid collisions with objects
// created simultaneously by the host platform.
// Host uses identifiers >= 2^16 and Dart is expected to use values n where,
// 0 <= n < 2^16.
static const int _maxDartCreatedIdentifier = 65536;
/// The default [$instanceManagerClassName] used by ProxyApis.
///
/// On creation, this manager makes a call to clear the native
/// InstanceManager. This is to prevent identifier conflicts after a host
/// restart.
static final $instanceManagerClassName instance = _initInstance();
// Expando is used because it doesn't prevent its keys from becoming
// inaccessible. This allows the manager to efficiently retrieve an identifier
// of an instance without holding a strong reference to that instance.
//
// It also doesn't use `==` to search for identifiers, which would lead to an
// infinite loop when comparing an object to its copy. (i.e. which was caused
// by calling instanceManager.getIdentifier() inside of `==` while this was a
// HashMap).
final Expando<int> _identifiers = Expando<int>();
final Map<int, WeakReference<$proxyApiBaseClassName>> _weakInstances =
<int, WeakReference<$proxyApiBaseClassName>>{};
final Map<int, $proxyApiBaseClassName> _strongInstances = <int, $proxyApiBaseClassName>{};
late final Finalizer<int> _finalizer;
int _nextIdentifier = 0;
/// Called when a weak referenced instance is removed by [removeWeakReference]
/// or becomes inaccessible.
late final void Function(int) onWeakReferenceRemoved;
static $instanceManagerClassName _initInstance() {
WidgetsFlutterBinding.ensureInitialized();
final _${instanceManagerClassName}Api api = _${instanceManagerClassName}Api();
// Clears the native `$instanceManagerClassName` on the initial use of the Dart one.
api.clear();
final $instanceManagerClassName instanceManager = $instanceManagerClassName(
onWeakReferenceRemoved: (int identifier) {
api.removeStrongReference(identifier);
},
);
_${instanceManagerClassName}Api.setUpMessageHandlers(instanceManager: instanceManager);
${apiHandlerSetUps.join('\n\t\t')}
return instanceManager;
}
/// Adds a new instance that was instantiated by Dart.
///
/// In other words, Dart wants to add a new instance that will represent
/// an object that will be instantiated on the host platform.
///
/// Throws assertion error if the instance has already been added.
///
/// Returns the randomly generated id of the [instance] added.
int addDartCreatedInstance($proxyApiBaseClassName instance) {
final int identifier = _nextUniqueIdentifier();
_addInstanceWithIdentifier(instance, identifier);
return identifier;
}
/// Removes the instance, if present, and call [onWeakReferenceRemoved] with
/// its identifier.
///
/// Returns the identifier associated with the removed instance. Otherwise,
/// `null` if the instance was not found in this manager.
///
/// This does not remove the strong referenced instance associated with
/// [instance]. This can be done with [remove].
int? removeWeakReference($proxyApiBaseClassName instance) {
final int? identifier = getIdentifier(instance);
if (identifier == null) {
return null;
}
_identifiers[instance] = null;
_finalizer.detach(instance);
onWeakReferenceRemoved(identifier);
return identifier;
}
/// Removes [identifier] and its associated strongly referenced instance, if
/// present, from the manager.
///
/// Returns the strong referenced instance associated with [identifier] before
/// it was removed. Returns `null` if [identifier] was not associated with
/// any strong reference.
///
/// This does not remove the weak referenced instance associated with
/// [identifier]. This can be done with [removeWeakReference].
T? remove<T extends $proxyApiBaseClassName>(int identifier) {
return _strongInstances.remove(identifier) as T?;
}
/// Retrieves the instance associated with identifier.
///
/// The value returned is chosen from the following order:
///
/// 1. A weakly referenced instance associated with identifier.
/// 2. If the only instance associated with identifier is a strongly
/// referenced instance, a copy of the instance is added as a weak reference
/// with the same identifier. Returning the newly created copy.
/// 3. If no instance is associated with identifier, returns null.
///
/// This method also expects the host `InstanceManager` to have a strong
/// reference to the instance the identifier is associated with.
T? getInstanceWithWeakReference<T extends $proxyApiBaseClassName>(int identifier) {
final $proxyApiBaseClassName? weakInstance = _weakInstances[identifier]?.target;
if (weakInstance == null) {
final $proxyApiBaseClassName? strongInstance = _strongInstances[identifier];
if (strongInstance != null) {
final $proxyApiBaseClassName copy = strongInstance.${classMemberNamePrefix}copy();
_identifiers[copy] = identifier;
_weakInstances[identifier] = WeakReference<$proxyApiBaseClassName>(copy);
_finalizer.attach(copy, identifier, detach: copy);
return copy as T;
}
return strongInstance as T?;
}
return weakInstance as T;
}
/// Retrieves the identifier associated with instance.
int? getIdentifier($proxyApiBaseClassName instance) {
return _identifiers[instance];
}
/// Adds a new instance that was instantiated by the host platform.
///
/// In other words, the host platform wants to add a new instance that
/// represents an object on the host platform. Stored with [identifier].
///
/// Throws assertion error if the instance or its identifier has already been
/// added.
///
/// Returns unique identifier of the [instance] added.
void addHostCreatedInstance($proxyApiBaseClassName instance, int identifier) {
_addInstanceWithIdentifier(instance, identifier);
}
void _addInstanceWithIdentifier($proxyApiBaseClassName instance, int identifier) {
assert(!containsIdentifier(identifier));
assert(getIdentifier(instance) == null);
assert(identifier >= 0);
_identifiers[instance] = identifier;
_weakInstances[identifier] = WeakReference<$proxyApiBaseClassName>(instance);
_finalizer.attach(instance, identifier, detach: instance);
final $proxyApiBaseClassName copy = instance.${classMemberNamePrefix}copy();
_identifiers[copy] = identifier;
_strongInstances[identifier] = copy;
}
/// Whether this manager contains the given [identifier].
bool containsIdentifier(int identifier) {
return _weakInstances.containsKey(identifier) ||
_strongInstances.containsKey(identifier);
}
int _nextUniqueIdentifier() {
late int identifier;
do {
identifier = _nextIdentifier;
_nextIdentifier = (_nextIdentifier + 1) % _maxDartCreatedIdentifier;
} while (containsIdentifier(identifier));
return identifier;
}
}
''';
}
/// The base class for all ProxyApis.
///
/// All Dart classes generated as a ProxyApi extends this one.
const String proxyApiBaseClass = '''
/// An immutable object that serves as the base class for all ProxyApis and
/// can provide functional copies of itself.
///
/// All implementers are expected to be [immutable] as defined by the annotation
/// and override [${classMemberNamePrefix}copy] returning an instance of itself.
@immutable
abstract class $proxyApiBaseClassName {
/// Construct a [$proxyApiBaseClassName].
$proxyApiBaseClassName({
this.$_proxyApiBaseClassMessengerVarName,
$instanceManagerClassName? $_proxyApiBaseClassInstanceManagerVarName,
}) : $_proxyApiBaseClassInstanceManagerVarName =
$_proxyApiBaseClassInstanceManagerVarName ?? $instanceManagerClassName.instance;
/// Sends and receives binary data across the Flutter platform barrier.
///
/// If it is null, the default BinaryMessenger will be used, which routes to
/// the host platform.
@protected
final BinaryMessenger? $_proxyApiBaseClassMessengerVarName;
/// Maintains instances stored to communicate with native language objects.
@protected
final $instanceManagerClassName $_proxyApiBaseClassInstanceManagerVarName;
/// Instantiates and returns a functionally identical object to oneself.
///
/// Outside of tests, this method should only ever be called by
/// [$instanceManagerClassName].
///
/// Subclasses should always override their parent's implementation of this
/// method.
@protected
$proxyApiBaseClassName ${classMemberNamePrefix}copy();
}
''';
/// The base codec for ProxyApis.
///
/// All generated Dart proxy apis should use this codec or extend it. This codec
/// adds support to convert instances to their corresponding identifier from an
/// `InstanceManager` and vice versa.
const String proxyApiBaseCodec = '''
class $_proxyApiCodecName extends _PigeonCodec {
const $_proxyApiCodecName(this.instanceManager);
final $instanceManagerClassName instanceManager;
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is $proxyApiBaseClassName) {
buffer.putUint8(128);
writeValue(buffer, instanceManager.getIdentifier(value));
} else {
super.writeValue(buffer, value);
}
}
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
return instanceManager
.getInstanceWithWeakReference(readValue(buffer)! as int);
default:
return super.readValueOfType(type, buffer);
}
}
}
''';
/// Name of the base class of all ProxyApis.
const String proxyApiBaseClassName = '${classNamePrefix}ProxyApiBaseClass';
const String _proxyApiBaseClassMessengerVarName =
'${classMemberNamePrefix}binaryMessenger';
const String _proxyApiBaseClassInstanceManagerVarName =
'${classMemberNamePrefix}instanceManager';
const String _proxyApiCodecName = '_${classNamePrefix}ProxyApiBaseCodec';