[dart-sdk] Add Module.createEntity api
TEST=
- fx set x64 --packages topaz/packages/buildbot --packages
vendor/google/packages/buildbot
- fx run-host-tess
Change-Id: I1d8380d323e36a8bccfee628ed7552226ab854fd
diff --git a/examples/modular/models/BUILD.gn b/examples/modular/models/BUILD.gn
index c5941ae..18bcbcb 100644
--- a/examples/modular/models/BUILD.gn
+++ b/examples/modular/models/BUILD.gn
@@ -12,7 +12,6 @@
sources = [
"shape.dart",
"src/shape.dart",
- "src/shape_codec.dart",
]
deps = [
diff --git a/examples/modular/models/lib/shape.dart b/examples/modular/models/lib/shape.dart
index 7cd7d32..99e76c5 100644
--- a/examples/modular/models/lib/shape.dart
+++ b/examples/modular/models/lib/shape.dart
@@ -3,4 +3,3 @@
// found in the LICENSE file.
export 'src/shape.dart';
-export 'src/shape_codec.dart';
diff --git a/examples/modular/models/lib/src/shape.dart b/examples/modular/models/lib/src/shape.dart
index e23b5b2..c7b13a6 100644
--- a/examples/modular/models/lib/src/shape.dart
+++ b/examples/modular/models/lib/src/shape.dart
@@ -2,8 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:typed_data';
+
/// A Simple model object representing a shape
class Shape {
+ static const String entityType = 'com.fuchsia.shapes_mod.shape';
+
/// The size of the shape
double size = 0.0;
@@ -15,4 +19,16 @@
/// The default constructor
Shape(this.size);
+
+ Shape.fromBytes(Uint8List bytes) {
+ size = ByteData.view(bytes.buffer).getFloat64(0);
+ }
+
+ Uint8List toBytes() {
+ final list = Uint8List(8);
+
+ ByteData.view(list.buffer).setFloat64(0, size);
+
+ return list;
+ }
}
diff --git a/examples/modular/models/lib/src/shape_codec.dart b/examples/modular/models/lib/src/shape_codec.dart
deleted file mode 100644
index dd486d2..0000000
--- a/examples/modular/models/lib/src/shape_codec.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2018 The Fuchsia 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 'dart:typed_data';
-
-import 'package:fuchsia_modular/entity.dart';
-
-import 'shape.dart';
-
-/// An [EntityCodec] which encodes and decodes a shape object
-class ShapeCodec extends SimpleEntityCodec<Shape> {
- ShapeCodec()
- : super(
- type: 'com.fuchsia.shapes_mod.shape',
- encoding: '*',
- encode: _encode,
- decode: _decode);
-
- static Uint8List _encode(Shape shape) {
- final list = Uint8List(8);
-
- ByteData.view(list.buffer).setFloat64(0, shape.size);
-
- return list;
- }
-
- static Shape _decode(Uint8List list) {
- final size = ByteData.view(list.buffer).getFloat64(0);
- return Shape(size);
- }
-}
diff --git a/examples/modular/shapes_mod/lib/src/intent_handlers/root_intent_handler.dart b/examples/modular/shapes_mod/lib/src/intent_handlers/root_intent_handler.dart
index 1207504..4a082ce 100644
--- a/examples/modular/shapes_mod/lib/src/intent_handlers/root_intent_handler.dart
+++ b/examples/modular/shapes_mod/lib/src/intent_handlers/root_intent_handler.dart
@@ -15,9 +15,9 @@
@override
void handleIntent(Intent intent) {
final stream = intent
- .getEntity(name: 'shape', codec: ShapeCodec())
+ .getEntity(name: 'shape', type: Shape.entityType)
.watch()
- .cast<Shape>();
+ .map((b) => Shape.fromBytes(b));
if (intent.action == _circleAction) {
CircleRenderer().render(stream);
diff --git a/examples/modular/shapes_mod/meta/shapes_mod.cmx b/examples/modular/shapes_mod/meta/shapes_mod.cmx
index d297d04..be82af8 100644
--- a/examples/modular/shapes_mod/meta/shapes_mod.cmx
+++ b/examples/modular/shapes_mod/meta/shapes_mod.cmx
@@ -9,6 +9,7 @@
"fuchsia.logger.LogSink",
"fuchsia.modular.Clipboard",
"fuchsia.modular.ContextWriter",
+ "fuchsia.modular.ComponentContext",
"fuchsia.modular.ModuleContext",
"fuchsia.netstack.Netstack",
"fuchsia.sys.Environment",
diff --git a/examples/modular/slider_mod/lib/src/blocs/app_bloc.dart b/examples/modular/slider_mod/lib/src/blocs/app_bloc.dart
index c23045e..2411a38 100644
--- a/examples/modular/slider_mod/lib/src/blocs/app_bloc.dart
+++ b/examples/modular/slider_mod/lib/src/blocs/app_bloc.dart
@@ -13,18 +13,22 @@
AppBloc(this.shapeEntity);
- void launchSquare() => Module().addModuleToStory(
- name: 'shape_module',
- intent: _makeIntent('com.fuchsia.shapes_mod.show_square'));
+ void launchSquare() =>
+ _launchModule('shape_module', 'com.fuchsia.shapes_mod.show_square');
- void launchCircle() => Module().addModuleToStory(
- name: 'shape_module',
- intent: _makeIntent('com.fuchsia.shapes_mod.show_circle'));
+ void launchCircle() =>
+ _launchModule('shape_module', 'com.fuchsia.shapes_mod.show_circle');
- Intent _makeIntent(String action) => Intent(
+ void _launchModule(String name, String action) {
+ _makeIntent(action).then(
+ (intent) => Module().addModuleToStory(name: name, intent: intent));
+ }
+
+ Future<Intent> _makeIntent(String action) async => Intent(
action: action,
- handler: 'shapes_mod',
- )..addParameterFromEntity('shape', shapeEntity);
+ handler: 'fuchsia-pkg://fuchsia.com/shapes_mod#meta/shapes_mod.cmx',
+ )..addParameterFromEntityReference(
+ 'shape', await shapeEntity.getEntityReference());
@override
void dispose() {}
diff --git a/examples/modular/slider_mod/lib/src/blocs/slider_bloc.dart b/examples/modular/slider_mod/lib/src/blocs/slider_bloc.dart
index ab6e744..ee3e9a8 100644
--- a/examples/modular/slider_mod/lib/src/blocs/slider_bloc.dart
+++ b/examples/modular/slider_mod/lib/src/blocs/slider_bloc.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:typed_data';
import 'package:example_modular_models/shape.dart';
import 'package:fuchsia_modular/entity.dart';
@@ -12,7 +13,7 @@
/// The [SliderBloc] provides actions and streams associated with
/// the onscreen slider.
class SliderBloc implements BlocBase {
- final Entity<Shape> shapeEntity;
+ final Entity<Uint8List> shapeEntity;
final Shape shape;
final _valueController = StreamController<double>.broadcast();
@@ -34,6 +35,6 @@
_valueController.add(newValue);
shape.size = newValue;
- shapeEntity.write(shape);
+ shapeEntity.write(shape.toBytes());
}
}
diff --git a/examples/modular/slider_mod/lib/src/handlers/root_intent_handler.dart b/examples/modular/slider_mod/lib/src/handlers/root_intent_handler.dart
index 4b5b08f..2346a7a 100644
--- a/examples/modular/slider_mod/lib/src/handlers/root_intent_handler.dart
+++ b/examples/modular/slider_mod/lib/src/handlers/root_intent_handler.dart
@@ -4,7 +4,6 @@
import 'package:example_modular_models/shape.dart';
import 'package:flutter/material.dart';
-import 'package:fuchsia_modular/entity.dart';
import 'package:fuchsia_modular/module.dart';
import '../blocs/app_bloc.dart';
@@ -13,14 +12,16 @@
class RootIntentHandler extends IntentHandler {
@override
- void handleIntent(Intent intent) {
+ void handleIntent(Intent intent) async {
// if (intent.action != '') {
// throw Exception('Unknown intent action');
// }
final shape = Shape(5.0);
-
- final shapeEntity = Entity(codec: ShapeCodec())..write(shape);
+ final shapeEntity = await Module().createEntity(
+ type: Shape.entityType,
+ initialData: shape.toBytes(),
+ );
runApp(
MaterialApp(
diff --git a/examples/modular/slider_mod/lib/src/widgets/slider_scaffold.dart b/examples/modular/slider_mod/lib/src/widgets/slider_scaffold.dart
index c860b8c..22637e8 100644
--- a/examples/modular/slider_mod/lib/src/widgets/slider_scaffold.dart
+++ b/examples/modular/slider_mod/lib/src/widgets/slider_scaffold.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:typed_data';
+
import 'package:example_modular_models/shape.dart';
import 'package:flutter/material.dart';
import 'package:fuchsia_modular/entity.dart';
@@ -12,7 +14,7 @@
import 'value_slider.dart';
class SliderScaffold extends StatelessWidget {
- final Entity<Shape> shapeEntity;
+ final Entity<Uint8List> shapeEntity;
final Shape shape;
const SliderScaffold(this.shapeEntity, this.shape);
diff --git a/examples/modular/slider_mod/meta/slider_mod.cmx b/examples/modular/slider_mod/meta/slider_mod.cmx
index bdc92e8..5318f02 100644
--- a/examples/modular/slider_mod/meta/slider_mod.cmx
+++ b/examples/modular/slider_mod/meta/slider_mod.cmx
@@ -21,7 +21,8 @@
"facets": {
"fuchsia.module": {
"@version": 2,
- "suggestion_headline": "Show a slider"
+ "suggestion_headline": "Show a slider",
+ "intent_filters": []
}
}
}
diff --git a/public/dart/fuchsia_modular/BUILD.gn b/public/dart/fuchsia_modular/BUILD.gn
index 42c7ac0..1f2a8f7 100644
--- a/public/dart/fuchsia_modular/BUILD.gn
+++ b/public/dart/fuchsia_modular/BUILD.gn
@@ -17,7 +17,6 @@
"agent.dart",
"codecs.dart",
"entity.dart",
- "entity_codec.dart",
"lifecycle.dart",
"logger.dart",
"module.dart",
@@ -29,6 +28,8 @@
"src/agent/internal/_agent_impl.dart",
"src/entity/entity.dart",
"src/entity/entity_codec.dart",
+ "src/entity/entity_exceptions.dart",
+ "src/entity/internal/_entity_impl.dart",
"src/entity/internal/_link_entity.dart",
"src/internal/_component_context.dart",
"src/lifecycle/internal/_lifecycle_impl.dart",
@@ -53,6 +54,7 @@
"//garnet/public/fidl/fuchsia.sys",
"//garnet/public/fidl/fuchsia.ui.viewsv1token",
"//peridot/public/fidl/fuchsia.modular",
+ "//third_party/dart-pkg/pub/async",
"//third_party/dart-pkg/pub/meta",
"//third_party/dart-pkg/pub/uuid",
"//topaz/public/dart/fidl",
@@ -105,6 +107,7 @@
]
sources = [
+ "entity/internal/entity_impl_test.dart",
"internal/component_context_integ_test.dart",
"lifecycle/internal/lifecycle_impl_test.dart",
"module/internal/module_impl_integ_test.dart",
diff --git a/public/dart/fuchsia_modular/lib/entity.dart b/public/dart/fuchsia_modular/lib/entity.dart
index c88f58a..5d138c5 100644
--- a/public/dart/fuchsia_modular/lib/entity.dart
+++ b/public/dart/fuchsia_modular/lib/entity.dart
@@ -5,3 +5,4 @@
/// The classes required for working with enities
export 'src/entity/entity.dart';
export 'src/entity/entity_codec.dart';
+export 'src/entity/entity_exceptions.dart';
diff --git a/public/dart/fuchsia_modular/lib/src/entity/entity.dart b/public/dart/fuchsia_modular/lib/src/entity/entity.dart
index 6c0f51d..e7b05ac 100644
--- a/public/dart/fuchsia_modular/lib/src/entity/entity.dart
+++ b/public/dart/fuchsia_modular/lib/src/entity/entity.dart
@@ -3,15 +3,20 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:typed_data';
+import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl_modular;
import 'package:meta/meta.dart';
import 'package:uuid/uuid.dart';
+import '../internal/_component_context.dart';
import 'entity_codec.dart';
+import 'entity_exceptions.dart';
+import 'internal/_entity_impl.dart';
import 'internal/_link_entity.dart';
/// An [Entity] provides a mechanism for communicating
-/// data between compoonents.
+/// data between components.
///
/// Note: this is a preliminary API that is likely to change.
@experimental
@@ -19,19 +24,74 @@
/// Creates an entity that will live for the scope of this story.
/// The entity that is created will be backed by the framework and
/// can be treated as if it was received from any other entity provider.
+ ///
+ /// Note: EntityCodec will be removed after soft transition
factory Entity({
- @required EntityCodec<T> codec,
+ EntityCodec<T> codec,
+ String type,
+ String entityReference,
}) {
- // This is temporary and go away when we remove link entities.
- final linkName = Uuid().v4().toString();
- return LinkEntity<T>(linkName: linkName, codec: codec);
+ if (codec != null) {
+ // This is temporary and go away when we remove link entities.
+ final linkName = Uuid().v4().toString();
+ return LinkEntity<T>(linkName: linkName, codec: codec);
+ } else if (T is Uint8List) {
+ return Entity._resolved(entityReference: entityReference, type: type);
+ }
+ return null;
}
+ /// Creates an Entity instance.
+ ///
+ /// This method will lazily connect to the entity proxy for data transmission.
+ /// This object should be treated like it has a valid connection until
+ /// otherwise informed via a failed future.
+ ///
+ /// ```
+ /// final entity = Entity(entityReference: 'foo', type: 'com.foo.bar');
+ /// // fetch the data assuming that the entity resolved correctly. If it
+ /// // did not the call to getData() will fail.
+ /// final data = await entity.getData();
+ /// ```
+ // note: hidden and static until soft transition completes at which point
+ // will become `factory Entity(...)`
+ // ignore: unused_element
+ static Entity _resolved({
+ @required String entityReference,
+ @required String type,
+ }) {
+ ArgumentError.checkNotNull(entityReference, 'entityReference');
+ ArgumentError.checkNotNull(type, 'type');
+
+ return EntityImpl(
+ proxyFactory: () async {
+ final resolver = fidl_modular.EntityResolverProxy();
+ await getComponentContext().getEntityResolver(resolver.ctrl.request());
+
+ final proxy = fidl_modular.EntityProxy();
+ await resolver.resolveEntity(entityReference, proxy.ctrl.request());
+
+ final types = await proxy.getTypes();
+ if (!types.contains(type)) {
+ throw EntityTypeException(type);
+ }
+
+ return proxy;
+ },
+ type: type,
+ );
+ }
+
+ /// The type of data that this object represents.
+ String get type;
+
/// Returns the data stored in the entity.
Future<T> getData();
- /// Writes the object stored in value
- Future<void> write(T object);
+ /// Returns the reference for this entity. Entity references will never change
+ /// for a given entity so this value can be cached and used to access the
+ /// entity from a different process or at a later time.
+ Future<String> getEntityReference();
/// Watches the entity for updates.
///
@@ -42,18 +102,7 @@
/// which, when closed, will close the underlying fidl
/// connection.
Stream<T> watch();
-}
-/// An exception which is thrown when an Entity does not
-/// support a given type.
-class EntityTypeException implements Exception {
- /// The unsuported type.
- final String type;
-
- /// Create a new [EntityTypeException].
- EntityTypeException(this.type);
-
- @override
- String toString() =>
- 'EntityTypeError: type "$type" is not available for Entity';
+ /// Writes the object stored in value
+ Future<void> write(T object);
}
diff --git a/public/dart/fuchsia_modular/lib/src/entity/entity_exceptions.dart b/public/dart/fuchsia_modular/lib/src/entity/entity_exceptions.dart
new file mode 100644
index 0000000..7d49b12
--- /dev/null
+++ b/public/dart/fuchsia_modular/lib/src/entity/entity_exceptions.dart
@@ -0,0 +1,33 @@
+// Copyright 2018 The Fuchsia 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 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl_modular;
+
+/// An exception which is thrown when an Entity does not
+/// support a given type.
+class EntityTypeException implements Exception {
+ /// The unsuported type.
+ final String type;
+
+ /// Create a new [EntityTypeException].
+ EntityTypeException(this.type);
+
+ @override
+ String toString() =>
+ 'EntityTypeError: type "$type" is not available for Entity';
+}
+
+/// An exception which is thrown when writes to an Entity
+/// fail with the give status.
+class EntityWriteException implements Exception {
+ /// The status code for this failure.
+ final fidl_modular.EntityWriteStatus status;
+
+ /// Create a new [EntityWriteException].
+ EntityWriteException(this.status);
+
+ @override
+ String toString() =>
+ 'EntityWriteException: entity write failed with status $status';
+}
diff --git a/public/dart/fuchsia_modular/lib/src/entity/internal/_entity_impl.dart b/public/dart/fuchsia_modular/lib/src/entity/internal/_entity_impl.dart
new file mode 100644
index 0000000..8daf70f
--- /dev/null
+++ b/public/dart/fuchsia_modular/lib/src/entity/internal/_entity_impl.dart
@@ -0,0 +1,113 @@
+// Copyright 2018 The Fuchsia 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 'dart:async';
+import 'dart:typed_data';
+
+import 'package:async/async.dart';
+import 'package:fidl/fidl.dart';
+import 'package:fidl_fuchsia_mem/fidl_async.dart' as fuchsia_mem;
+import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl_modular;
+import 'package:meta/meta.dart';
+import 'package:zircon/zircon.dart';
+
+import '../entity.dart';
+import '../entity_exceptions.dart';
+
+typedef EntityProxyFactory = FutureOr<fidl_modular.Entity> Function();
+
+/// A concrete implementation of the [Entity] class.
+class EntityImpl implements Entity<Uint8List> {
+ final _proxyMemo = AsyncMemoizer<fidl_modular.Entity>();
+
+ @override
+ final String type;
+
+ final _entityReferenceMemo = AsyncMemoizer<String>();
+
+ /// A function which returns a bound proxy on demand. This method will only be
+ /// called once.
+ final EntityProxyFactory proxyFactory;
+
+ /// Constructs an instance of [EntityImpl].
+ EntityImpl({
+ @required this.proxyFactory,
+ @required this.type,
+ }) : assert(proxyFactory != null),
+ assert(type != null && type.isNotEmpty);
+
+ @override
+ Future<Uint8List> getData() =>
+ _getProxy().then((p) => p.getData(type)).then(_unwrapBuffer);
+
+ @override
+ Future<String> getEntityReference() => _entityReferenceMemo
+ .runOnce(() => _getProxy().then((p) => p.getReference()));
+
+ @override
+ Stream<Uint8List> watch() {
+ final controller = StreamController<fuchsia_mem.Buffer>();
+ final watcher = _EntityWatcher(controller.add);
+
+ // if the user closed the stream we close the binding to the watcher
+ controller.onCancel = watcher.binding.close;
+
+ // if the connection closes then we close the stream
+ watcher.binding.whenClosed.then((_) {
+ if (!controller.isClosed) {
+ controller.close();
+ }
+ });
+
+ _getProxy().then((proxy) {
+ proxy.watch(type, watcher.getInterfaceHandle());
+ });
+
+ return controller.stream.asyncMap(_unwrapBuffer);
+ }
+
+ @override
+ Future<void> write(Uint8List value) async {
+ ArgumentError.checkNotNull(value, 'value');
+
+ final vmo = SizedVmo.fromUint8List(value);
+ final buffer = fuchsia_mem.Buffer(vmo: vmo, size: value.length);
+
+ final proxy = await _getProxy();
+ final result = await proxy.writeData(type, buffer);
+ if (result != fidl_modular.EntityWriteStatus.ok) {
+ throw EntityWriteException(result);
+ }
+ }
+
+ Future<fidl_modular.Entity> _getProxy() => _proxyMemo.runOnce(proxyFactory);
+
+ Uint8List _unwrapBuffer(fuchsia_mem.Buffer buffer) {
+ final dataVmo = SizedVmo(buffer.vmo.handle, buffer.size);
+ final result = dataVmo.read(buffer.size);
+
+ if (result.status != 0) {
+ throw Exception(
+ 'VMO read faile with status [${result.status}] when trying to read entity data with type [$type]');
+ }
+
+ dataVmo.close();
+ return result.bytesAsUint8List();
+ }
+}
+
+class _EntityWatcher implements fidl_modular.EntityWatcher {
+ final fidl_modular.EntityWatcherBinding binding =
+ fidl_modular.EntityWatcherBinding();
+ final void Function(fuchsia_mem.Buffer) onUpdatedFunc;
+
+ _EntityWatcher(this.onUpdatedFunc) : assert(onUpdatedFunc != null);
+
+ InterfaceHandle<fidl_modular.EntityWatcher> getInterfaceHandle() {
+ return binding.wrap(this);
+ }
+
+ @override
+ Future<void> onUpdated(fuchsia_mem.Buffer data) async => onUpdatedFunc(data);
+}
diff --git a/public/dart/fuchsia_modular/lib/src/entity/internal/_link_entity.dart b/public/dart/fuchsia_modular/lib/src/entity/internal/_link_entity.dart
index dc59146..f580a64 100644
--- a/public/dart/fuchsia_modular/lib/src/entity/internal/_link_entity.dart
+++ b/public/dart/fuchsia_modular/lib/src/entity/internal/_link_entity.dart
@@ -16,6 +16,7 @@
import '../../module/internal/_module_context.dart';
import '../entity.dart';
import '../entity_codec.dart';
+import '../entity_exceptions.dart';
/// An [Entity] implementation which supports
/// data that lives inside links.
@@ -38,9 +39,15 @@
assert(codec != null);
@override
+ String get type => codec.type;
+
+ @override
Future<T> getData() async => _getSnapshot();
@override
+ Future<String> getEntityReference() => null;
+
+ @override
Stream<T> watch() {
final link = _getLink();
final controller = StreamController<fuchsia_mem.Buffer>();
diff --git a/public/dart/fuchsia_modular/lib/src/module/intent.dart b/public/dart/fuchsia_modular/lib/src/module/intent.dart
index 38e3531..d139360 100644
--- a/public/dart/fuchsia_modular/lib/src/module/intent.dart
+++ b/public/dart/fuchsia_modular/lib/src/module/intent.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:typed_data';
+
import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl;
import 'package:meta/meta.dart';
@@ -94,12 +96,35 @@
@experimental
Entity getEntity({
@required String name,
- @required EntityCodec codec,
- }) =>
- _entityFromIntentParameter(
+ EntityCodec codec,
+ String type,
+ }) {
+ if (codec != null) {
+ return _entityFromIntentParameter(
parameter: _getParameter(name),
codec: codec,
);
+ } else {
+ return _getEntity(name: name, type: type);
+ }
+ }
+
+ /// Returns the entity with the given [name]. An entity's name maps to
+ /// the name of a parameter in your component's manifset.
+ ///
+ /// The type is used to ensure the entity provider provides us with the
+ /// type of entity we are expecting. If the type does not match the entity
+ /// will fail to resolve.
+ // Note: this method will be renamed to getEntity when the soft transition completes.
+ Entity _getEntity({
+ @required String name,
+ @required String type,
+ }) {
+ ArgumentError.checkNotNull(name, 'name');
+ ArgumentError.checkNotNull(type, 'type');
+ return _entityFromIntentParameter(
+ parameter: _getParameter(name), type: type);
+ }
void _addParameter(String name, fidl.IntentParameterData parameterData) =>
parameters.add(fidl.IntentParameter(name: name, data: parameterData));
@@ -110,10 +135,15 @@
Entity _entityFromIntentParameter({
fidl.IntentParameter parameter,
+ String type,
EntityCodec codec,
}) {
if (parameter.data.tag == fidl.IntentParameterDataTag.linkName) {
return LinkEntity(linkName: parameter.data.linkName, codec: codec);
+ } else if (parameter.data.tag ==
+ fidl.IntentParameterDataTag.entityReference) {
+ return Entity<Uint8List>(
+ type: type, entityReference: parameter.data.entityReference);
}
throw UnimplementedError();
}
diff --git a/public/dart/fuchsia_modular/lib/src/module/internal/_module_impl.dart b/public/dart/fuchsia_modular/lib/src/module/internal/_module_impl.dart
index 8af6697..8cd0bbe 100644
--- a/public/dart/fuchsia_modular/lib/src/module/internal/_module_impl.dart
+++ b/public/dart/fuchsia_modular/lib/src/module/internal/_module_impl.dart
@@ -3,13 +3,18 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:typed_data';
import 'package:fidl/fidl.dart';
+import 'package:fidl_fuchsia_mem/fidl_async.dart' as fuchsia_mem;
import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl;
import 'package:fidl_fuchsia_ui_viewsv1token/fidl_async.dart' as views_fidl;
import 'package:fuchsia_modular/lifecycle.dart';
import 'package:meta/meta.dart';
+import 'package:zircon/zircon.dart';
+import '../../entity/entity.dart';
+import '../../entity/internal/_entity_impl.dart';
import '../embedded_module.dart';
import '../intent.dart';
import '../intent_handler.dart';
@@ -80,6 +85,36 @@
}
@override
+ Future<Entity<Uint8List>> createEntity({
+ @required String type,
+ @required Uint8List initialData,
+ }) async {
+ ArgumentError.checkNotNull(type, 'type');
+ ArgumentError.checkNotNull(initialData, 'initialData');
+
+ if (type.isEmpty) {
+ throw ArgumentError.value(type, 'type cannot be an empty string');
+ }
+
+ final context = _getContext();
+
+ // need to create the proxy and write data immediately so other modules
+ // can extract values
+ final proxy = fidl.EntityProxy();
+ final vmo = SizedVmo.fromUint8List(initialData);
+ final buffer = fuchsia_mem.Buffer(vmo: vmo, size: initialData.length);
+ final ref = await context.createEntity(type, buffer, proxy.ctrl.request());
+
+ // use the ref value to determine if creation was successful
+ if (ref == null || ref.isEmpty) {
+ throw Exception('Module.createEntity creation failed because'
+ ' the framework was unable to create the entity.');
+ }
+
+ return EntityImpl(type: type, proxyFactory: () => proxy);
+ }
+
+ @override
Future<EmbeddedModule> embedModule({
@required String name,
@required fidl.Intent intent,
diff --git a/public/dart/fuchsia_modular/lib/src/module/module.dart b/public/dart/fuchsia_modular/lib/src/module/module.dart
index ef06e02..d265e52 100644
--- a/public/dart/fuchsia_modular/lib/src/module/module.dart
+++ b/public/dart/fuchsia_modular/lib/src/module/module.dart
@@ -3,11 +3,13 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:typed_data';
import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl;
import 'package:fuchsia_services/services.dart';
import 'package:meta/meta.dart';
+import '../entity/entity.dart';
import 'embedded_module.dart';
import 'intent_handler.dart';
import 'internal/_intent_handler_impl.dart';
@@ -83,6 +85,14 @@
fidl.SurfaceRelation surfaceRelation,
});
+ /// Creates an entity that will live for the lifetime of the story.
+ /// The entity that is created will be backed by the framework and
+ /// can be treated as if it was received from any other entity provider.
+ Future<Entity<Uint8List>> createEntity({
+ @required String type,
+ @required Uint8List initialData,
+ });
+
/// This method functions similarly to [addModuleToStory()], but instead
/// of relying on the story shell for display it is up to the caller to
/// display the view from the new module.
@@ -121,5 +131,4 @@
/// module and remove it from the story. If there are no more running modules
/// in the story the story will be stopped.
void removeSelfFromStory();
-
}
diff --git a/public/dart/fuchsia_modular/meta/fuchsia_modular_package_integration_tests.cmx b/public/dart/fuchsia_modular/meta/fuchsia_modular_package_integration_tests.cmx
index ea24f9b..71676e7 100644
--- a/public/dart/fuchsia_modular/meta/fuchsia_modular_package_integration_tests.cmx
+++ b/public/dart/fuchsia_modular/meta/fuchsia_modular_package_integration_tests.cmx
@@ -8,7 +8,9 @@
"fuchsia.fonts.Provider",
"fuchsia.logger.LogSink",
"fuchsia.modular.Clipboard",
+ "fuchsia.modular.ComponentContext",
"fuchsia.modular.ContextWriter",
+ "fuchsia.modular.Lifecycle",
"fuchsia.modular.ModuleContext",
"fuchsia.netstack.Netstack",
"fuchsia.sys.Environment",
diff --git a/public/dart/fuchsia_modular/test/entity/internal/entity_impl_test.dart b/public/dart/fuchsia_modular/test/entity/internal/entity_impl_test.dart
new file mode 100644
index 0000000..99bec26
--- /dev/null
+++ b/public/dart/fuchsia_modular/test/entity/internal/entity_impl_test.dart
@@ -0,0 +1,152 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: implementation_imports
+
+import 'dart:typed_data';
+
+import 'package:fidl/fidl.dart';
+import 'package:fidl_fuchsia_mem/fidl_async.dart' as fuchsia_mem;
+import 'package:fidl_fuchsia_modular/fidl_async.dart' as fuchsia_modular;
+import 'package:fidl_fuchsia_modular/fidl_test.dart' as fuchsia_modular_test;
+import 'package:fuchsia_modular/src/entity/entity_exceptions.dart';
+import 'package:fuchsia_modular/src/entity/internal/_entity_impl.dart';
+import 'package:test/test.dart';
+import 'package:zircon/zircon.dart';
+
+void main() {
+ MockProxy proxy;
+ EntityImpl entity;
+
+ group('enity interactions', () {
+ setUp(() {
+ proxy = MockProxy();
+ entity = EntityImpl(proxyFactory: () => proxy, type: 'foo');
+ });
+
+ tearDown(() {
+ proxy = null;
+ entity = null;
+ });
+ test('getData returns correct data', () async {
+ final data = Uint8List.fromList([1, 2, 3]);
+ await entity.write(data);
+ expect(await entity.getData(), data);
+ });
+
+ test('write returns success for valid write', () async {
+ final data = Uint8List.fromList([1, 2, 3]);
+ await entity.write(data);
+ // test will fail if write fails
+ });
+
+ test('write fails for error in write', () async {
+ proxy.writeStatus = fuchsia_modular.EntityWriteStatus.error;
+ final data = Uint8List.fromList([1, 2, 3]);
+ expect(entity.write(data),
+ throwsA(const TypeMatcher<EntityWriteException>()));
+ });
+
+ test('write fails for readonly', () async {
+ proxy.writeStatus = fuchsia_modular.EntityWriteStatus.readOnly;
+ final data = Uint8List.fromList([1, 2, 3]);
+ expect(entity.write(data),
+ throwsA(const TypeMatcher<EntityWriteException>()));
+ });
+
+ test('getEntityReference returns the correct ref', () async {
+ proxy.entityReference = 'my-entity';
+ expect(await entity.getEntityReference(), 'my-entity');
+ });
+
+ test('watch closes stream when binding is closed', () {
+ proxy.forceCloseWatchers = true;
+ final stream = entity.watch();
+ expect(stream, emitsInOrder([emitsDone]));
+ });
+
+ test('watch udpates stream', () async {
+ Uint8List _bufferWithValue(int v) {
+ final data = ByteData(1)..setInt8(0, v);
+ return data.buffer.asUint8List();
+ }
+
+ int _getValue(Uint8List buffer) {
+ return ByteData.view(buffer.buffer).getInt8(0);
+ }
+
+ final stream = entity.watch();
+
+ expect(stream.map(_getValue), emitsInOrder([1, 2, 3]));
+
+ await entity.write(_bufferWithValue(1));
+ await entity.write(_bufferWithValue(2));
+ await entity.write(_bufferWithValue(3));
+ });
+ });
+}
+
+class MockProxy extends fuchsia_modular_test.Entity$TestBase {
+ Uint8List _data;
+ String entityReference;
+ List<fuchsia_modular.EntityWatcherProxy> watchers = [];
+
+ /// if set to true the watchers will be closed after they are added.
+ bool forceCloseWatchers = false;
+
+ /// Can be set to control how write results are handled.
+ fuchsia_modular.EntityWriteStatus writeStatus =
+ fuchsia_modular.EntityWriteStatus.ok;
+
+ @override
+ Future<fuchsia_mem.Buffer> getData(String _) async {
+ final vmo = SizedVmo.fromUint8List(_data);
+ final buffer = fuchsia_mem.Buffer(vmo: vmo, size: _data.length);
+ return buffer;
+ }
+
+ @override
+ Future<String> getReference() async => entityReference;
+
+ @override
+ Future<void> watch(String type,
+ InterfaceHandle<fuchsia_modular.EntityWatcher> watcher) async {
+ final proxy = fuchsia_modular.EntityWatcherProxy();
+ proxy.ctrl.bind(watcher);
+ watchers.add(proxy);
+
+ if (forceCloseWatchers) {
+ //ignore: unawaited_futures
+ Future(proxy.ctrl.close);
+ }
+ }
+
+ @override
+ Future<fuchsia_modular.EntityWriteStatus> writeData(
+ String type, fuchsia_mem.Buffer buffer) async {
+ if (writeStatus != fuchsia_modular.EntityWriteStatus.ok) {
+ return writeStatus;
+ }
+
+ final dataVmo = SizedVmo(buffer.vmo.handle, buffer.size);
+ final result = dataVmo.read(buffer.size);
+ dataVmo.close();
+ final data = result.bytesAsUint8List();
+ _setData(data, notify: true);
+ return fuchsia_modular.EntityWriteStatus.ok;
+ }
+
+ void _setData(Uint8List data, {bool notify = false}) {
+ _data = data;
+ if (notify) {
+ final vmo = SizedVmo.fromUint8List(data);
+ final buffer = fuchsia_mem.Buffer(vmo: vmo, size: data.length);
+ for (final watcher in watchers) {
+ if (watcher.ctrl.isBound) {
+ watcher.onUpdated(buffer);
+ }
+ }
+ }
+ }
+}
diff --git a/public/dart/fuchsia_modular/test/module/intent_test.dart b/public/dart/fuchsia_modular/test/module/intent_test.dart
index 9ac71e7..1450b8c 100644
--- a/public/dart/fuchsia_modular/test/module/intent_test.dart
+++ b/public/dart/fuchsia_modular/test/module/intent_test.dart
@@ -2,12 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:convert';
-import 'dart:typed_data';
-
-import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl;
import 'package:fuchsia_modular/src/module/intent.dart'; // ignore: implementation_imports
-import 'package:fuchsia_modular/src/entity/entity_codec.dart'; // ignore: implementation_imports
+
import 'package:test/test.dart';
import '../matchers.dart';
@@ -41,28 +37,8 @@
test('getEntity throws for missing name', () {
expect(() {
- intent.getEntity(name: 'not-a-name', codec: stub);
+ intent.getEntity(name: 'not-a-name', type: 'foo');
}, throwsModuleStateException);
});
-
- test('getEntity returns valid entity for link entity', () {
- //NOTE: this test can be deleted when _link_entity goes away.
- intent.parameters.add(fidl.IntentParameter(
- name: 'name',
- data: fidl.IntentParameterData.withLinkName('foo-link')));
- final entity = intent.getEntity(name: 'name', codec: stub);
- expect(entity, isNotNull);
- });
});
}
-
-const StubEntityCodec stub = StubEntityCodec();
-
-class StubEntityCodec extends EntityCodec {
- const StubEntityCodec() : super(type: '', encoding: '');
- @override
- Converter<Uint8List, dynamic> get decoder => null;
-
- @override
- Converter<dynamic, Uint8List> get encoder => null;
-}