Flutter run restart (#4105)

* working on making a faster flutter run restart

* clean up todos; fire events on isolate changes

* use the Flutter.FrameworkInitialization event

* review comments
diff --git a/packages/flutter_tools/lib/src/observatory.dart b/packages/flutter_tools/lib/src/observatory.dart
index 27b83a4..93e9b10 100644
--- a/packages/flutter_tools/lib/src/observatory.dart
+++ b/packages/flutter_tools/lib/src/observatory.dart
@@ -13,6 +13,15 @@
     peer.registerMethod('streamNotify', (rpc.Parameters event) {
       _handleStreamNotify(event.asMap);
     });
+
+    onIsolateEvent.listen((Event event) {
+      if (event.kind == 'IsolateStart') {
+        _addIsolate(event.isolate);
+      } else if (event.kind == 'IsolateExit') {
+        String removedId = event.isolate.id;
+        isolates.removeWhere((IsolateRef ref) => ref.id == removedId);
+      }
+    });
   }
 
   static Future<Observatory> connect(int port) async {
@@ -26,19 +35,30 @@
   final rpc.Peer peer;
   final int port;
 
+  List<IsolateRef> isolates = <IsolateRef>[];
+  Completer<IsolateRef> _waitFirstIsolateCompleter;
+
   Map<String, StreamController<Event>> _eventControllers = <String, StreamController<Event>>{};
 
+  Set<String> _listeningFor = new Set<String>();
+
   bool get isClosed => peer.isClosed;
   Future<Null> get done => peer.done;
 
+  String get firstIsolateId => isolates.isEmpty ? null : isolates.first.id;
+
   // Events
 
+  Stream<Event> get onExtensionEvent => onEvent('Extension');
   // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
-  Stream<Event> get onIsolateEvent => _getEventController('Isolate').stream;
-  Stream<Event> get onTimelineEvent => _getEventController('Timeline').stream;
+  Stream<Event> get onIsolateEvent => onEvent('Isolate');
+  Stream<Event> get onTimelineEvent => onEvent('Timeline');
 
   // Listen for a specific event name.
-  Stream<Event> onEvent(String streamName) => _getEventController(streamName).stream;
+  Stream<Event> onEvent(String streamId) {
+    streamListen(streamId);
+    return _getEventController(streamId).stream;
+  }
 
   StreamController<Event> _getEventController(String eventName) {
     StreamController<Event> controller = _eventControllers[eventName];
@@ -54,16 +74,31 @@
     _getEventController(data['streamId']).add(event);
   }
 
+  Future<IsolateRef> get waitFirstIsolate async {
+    if (isolates.isNotEmpty)
+      return isolates.first;
+
+    _waitFirstIsolateCompleter = new Completer<IsolateRef>();
+
+    getVM().then((VM vm) {
+      for (IsolateRef isolate in vm.isolates)
+        _addIsolate(isolate);
+    });
+
+    return _waitFirstIsolateCompleter.future;
+  }
+
   // Requests
 
   Future<Response> sendRequest(String method, [Map<String, dynamic> args]) {
     return peer.sendRequest(method, args).then((dynamic result) => new Response(result));
   }
 
-  Future<Response> streamListen(String streamId) {
-    return sendRequest('streamListen', <String, dynamic>{
-      'streamId': streamId
-    });
+  Future<Null> streamListen(String streamId) async {
+    if (!_listeningFor.contains(streamId)) {
+      _listeningFor.add(streamId);
+      sendRequest('streamListen', <String, dynamic>{ 'streamId': streamId });
+    }
   }
 
   Future<VM> getVM() {
@@ -97,6 +132,17 @@
       'isolateId': isolateId
     }).then((dynamic result) => new Response(result));
   }
+
+  void _addIsolate(IsolateRef isolate) {
+    if (!isolates.contains(isolate)) {
+      isolates.add(isolate);
+
+      if (_waitFirstIsolateCompleter != null) {
+        _waitFirstIsolateCompleter.complete(isolate);
+        _waitFirstIsolateCompleter = null;
+      }
+    }
+  }
 }
 
 class Response {
@@ -104,6 +150,8 @@
 
   final Map<String, dynamic> response;
 
+  String get type => response['type'];
+
   dynamic operator[](String key) => response[key];
 
   @override
@@ -113,18 +161,35 @@
 class VM extends Response {
   VM(Map<String, dynamic> response) : super(response);
 
-  List<dynamic> get isolates => response['isolates'];
+  List<IsolateRef> get isolates => response['isolates'].map((dynamic ref) => new IsolateRef(ref)).toList();
 }
 
-class Event {
-  Event(this.event);
+class Event extends Response {
+  Event(Map<String, dynamic> response) : super(response);
 
-  final Map<String, dynamic> event;
+  String get kind => response['kind'];
+  IsolateRef get isolate => new IsolateRef.from(response['isolate']);
 
-  String get kind => event['kind'];
+  /// Only valid for [kind] == `Extension`.
+  String get extensionKind => response['extensionKind'];
+}
 
-  dynamic operator[](String key) => event[key];
+class IsolateRef extends Response {
+  IsolateRef(Map<String, dynamic> response) : super(response);
+  factory IsolateRef.from(dynamic ref) => ref == null ? null : new IsolateRef(ref);
+
+  String get id => response['id'];
 
   @override
-  String toString() => event.toString();
+  bool operator ==(dynamic other) {
+    if (identical(this, other))
+      return true;
+    if (other is! IsolateRef)
+      return false;
+    final IsolateRef typedOther = other;
+    return id == typedOther.id;
+  }
+
+  @override
+  int get hashCode => id.hashCode;
 }