| Index: sky/specs/events.md
|
| diff --git a/sky/specs/events.md b/sky/specs/events.md
|
| index d839c17059f205b963badcd5d981036cc334aa76..54d869603b6b8de5004d9c6f3f505afb1a40dfa2 100644
|
| --- a/sky/specs/events.md
|
| +++ b/sky/specs/events.md
|
| @@ -6,6 +6,111 @@ SKY MODULE
|
| <!-- part of sky:core -->
|
|
|
| <script>
|
| +import 'dart:collection';
|
| +import 'dart:async';
|
| +
|
| +class ExceptionAndStackTrace<T> {
|
| + const ExceptionAndStackTrace(this.exception, this.stackTrace);
|
| + final T exception;
|
| + final StackTrace stackTrace;
|
| +}
|
| +
|
| +class ExceptionListException<T> extends IterableMixin<ExceptionAndStackTrace<T>> implements Exception {
|
| + List<ExceptionAndStackTrace<T>> _exceptions;
|
| + void add(T exception, [StackTrace stackTrace = null]) {
|
| + if (_exceptions == null)
|
| + _exceptions = new List<ExceptionAndStackTrace<T>>();
|
| + _exceptions.add(new ExceptionAndStackTrace<T>(exception, stackTrace));
|
| + }
|
| + int get length => _exceptions == null ? 0 : _exceptions.length;
|
| + Iterator<ExceptionAndStackTrace<T>> get iterator => _exceptions.iterator;
|
| +}
|
| +
|
| +typedef bool Filter<T>(T t);
|
| +typedef void Handler<T>(T t);
|
| +
|
| +class DispatcherController<T> {
|
| + DispatcherController() : dispatcher = new Dispatcher<T>();
|
| + final Dispatcher<T> dispatcher;
|
| + void add(T data) => dispatcher._add(data);
|
| +}
|
| +
|
| +class Pair<A, B> {
|
| + const Pair(this.a, this.b);
|
| + final A a;
|
| + final B b;
|
| +}
|
| +
|
| +class Dispatcher<T> {
|
| + List<Pair<Handler, ZoneUnaryCallback>> _listeners;
|
| + void listen(Handler<T> handler) {
|
| + // you should not throw out of this handler
|
| + if (_listeners == null)
|
| + _listeners = new List<Pair<Handler, ZoneUnaryCallback>>();
|
| + _listeners.add(new Pair<Handler, ZoneUnaryCallback>(handler, Zone.current.bindUnaryCallback(handler)));
|
| + }
|
| + bool unlisten(Handler<T> handler) {
|
| + if (_listeners == null)
|
| + return false;
|
| + var target = _listeners.lastWhere((v) => v.a == handler, orElse: () => null);
|
| + if (target == null)
|
| + return false;
|
| + _listeners.remove(target);
|
| + return true;
|
| + }
|
| + void _add(T data) {
|
| + if (_listeners == null)
|
| + return;
|
| + ExceptionListException exceptions = new ExceptionListException();
|
| + // we make a copy of the list here so that the listeners can
|
| + // mutate our list without worry
|
| + _listeners.toList().forEach((Pair<Handler, ZoneUnaryCallback> item) {
|
| + try {
|
| + item.b(data);
|
| + } catch (exception, stackTrace) {
|
| + exceptions.add(exception, stackTrace);
|
| + }
|
| + });
|
| + if (exceptions.length > 0)
|
| + throw exceptions;
|
| + }
|
| +
|
| + Dispatcher<T> where(Filter<T> filter) {
|
| + var subdispatcher = new Dispatcher<T>();
|
| + listen((T data) {
|
| + if (filter(data))
|
| + subdispatcher._add(data);
|
| + });
|
| + return subdispatcher;
|
| + }
|
| +
|
| + Dispatcher<T> until(Filter<T> filter) {
|
| + var subdispatcher = new Dispatcher<T>();
|
| + Handler handler;
|
| + handler = (T data) {
|
| + if (filter(data))
|
| + unlisten(handler);
|
| + else
|
| + subdispatcher._add(data);
|
| + };
|
| + listen(handler);
|
| + return subdispatcher;
|
| + }
|
| +
|
| + Future<T> firstWhere(Filter<T> filter) {
|
| + Completer completer = new Completer();
|
| + Handler handler;
|
| + handler = (T data) {
|
| + if (filter(data)) {
|
| + completer.complete(data);
|
| + unlisten(handler);
|
| + }
|
| + };
|
| + listen(handler);
|
| + return completer.future;
|
| + }
|
| +}
|
| +
|
| abstract class Event<ReturnType> {
|
| Event() { init(); }
|
| void init() { }
|
| @@ -29,12 +134,12 @@ abstract class Event<ReturnType> {
|
| }
|
|
|
| class EventTarget {
|
| - EventTarget() : _eventsController = new DispatcherController<@nonnull Event>();
|
| + EventTarget() : _eventsController = new DispatcherController<Event>();
|
|
|
| Dispatcher get events => _eventsController.dispatcher;
|
| EventTarget parentNode;
|
|
|
| - List<@nonnull EventTarget> getEventDispatchChain() {
|
| + List<EventTarget> getEventDispatchChain() {
|
| if (this.parentNode == null) {
|
| return [this];
|
| } else {
|
| @@ -46,7 +151,7 @@ class EventTarget {
|
|
|
| final DispatcherController _eventsController;
|
|
|
| - dynamic dispatchEvent(@nonnull Event event, { dynamic defaultResult: null }) { // O(N*M) where N is the length of the chain and M is the average number of listeners per link in the chain
|
| + dynamic dispatchEvent(Event event, { dynamic defaultResult: null }) { // O(N*M) where N is the length of the chain and M is the average number of listeners per link in the chain
|
| // note: this will throw an ExceptionListException<ExceptionListException> if any of the listeners threw
|
| assert(event != null); // event must be non-null
|
| event.handled = false;
|
| @@ -71,7 +176,7 @@ class EventTarget {
|
| return event.result;
|
| }
|
|
|
| - void _dispatchEventLocally(@nonnull Event event) {
|
| + void _dispatchEventLocally(Event event) {
|
| event._currentTarget = this;
|
| _eventsController.add(event);
|
| }
|
|
|