OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** | |
6 * Accumulates change events from several observable objects. | |
7 * | |
8 * wrap() is public and used by client code. The other methods are used by | |
9 * AbstractObservable, which works with this class to implement batching. | |
10 */ | |
11 class EventBatch { | |
12 | |
13 /** The current active batch, if any. */ | |
14 static EventBatch current; | |
15 | |
16 /** Used to generate unique ids for observable objects. */ | |
17 static int nextUid; | |
18 | |
19 /** Map from observable object's uid to their tracked events. */ | |
20 // TODO(sigmund): use [Observable] instead of [int] when [Map] can support it, | |
21 Map<int, EventSummary> summaries; | |
22 | |
23 /** Whether this batch is currently firing and therefore is sealed. */ | |
24 bool sealed = false; | |
25 | |
26 /** | |
27 * Private constructor that shouldn't be used externally. Use [wrap] to ensure | |
28 * that a batch exists when running a function. | |
29 */ | |
30 EventBatch._internal() : summaries = new Map<int, EventSummary>(); | |
31 | |
32 /** | |
33 * Ensure there is an event batch where [userFunction] can accumuluate events. | |
34 * When the batch is complete, fire all events at once. | |
35 */ | |
36 static Function wrap(userFunction(var a)) { | |
37 return (e) { | |
38 if (current == null) { | |
39 // Not in a batch so create one. | |
40 final batch = new EventBatch._internal(); | |
41 current = batch; | |
42 var result = null; | |
43 try { | |
44 // TODO(jmesserly): don't return here, otherwise an exception in | |
45 // the finally clause will cause it to rerun. See bug#5350131. | |
46 result = userFunction(e); | |
47 } finally { | |
48 assert(current == batch); // no one should've changed this | |
49 // TODO(jmesserly): VM doesn't seem to like nested try/finally, so | |
50 // set current to null before _notify. That will ensure we're back | |
51 // to the right state, even if _notify throws. | |
52 current = null; | |
53 batch._notify(); | |
54 } | |
55 return result; | |
56 } else { | |
57 // Already in a batch, so just use it. | |
58 // TODO(rnystrom): Re-entrant calls to wrap() are kind of hairy. They | |
59 // can occur in at least one known place: | |
60 // 1. You respond to an event handler by calling a function with wrap() | |
61 // (i.e. the normal way we wrap event handlers). | |
62 // 2. In that handler, you spawn an XHR. You give it a callback which | |
63 // is also calling wrap, so that when it's later invoked, that is in | |
64 // a batch too. | |
65 // 3. Because of an error the XHR fails and calls the callback | |
66 // immediately instead of unwinding the stack past the first wrap() | |
67 // and then calling it asynchronously. | |
68 // This check handles that, but ideally we'd have a more elegant way of | |
69 // notifying after a series of changes like a onEventHandlerFinished | |
70 // event or something built into the DOM API. | |
71 return userFunction(e); | |
72 } | |
73 }; | |
74 } | |
75 | |
76 /** Returns a unique global id for observable objects. */ | |
77 static int genUid() { | |
78 if (nextUid == null) { | |
79 nextUid = 1; | |
80 } | |
81 return nextUid++; | |
82 } | |
83 | |
84 /** Retrieves the events associated with {@code obj}. */ | |
85 EventSummary getEvents(Observable obj) { | |
86 int uid = obj.uid; | |
87 EventSummary summary = summaries[uid]; | |
88 if (summary == null) { | |
89 assert (!sealed); | |
90 summary = new EventSummary(obj); | |
91 summaries[uid] = summary; | |
92 } | |
93 return summary; | |
94 } | |
95 | |
96 /** Fires all events at once. */ | |
97 void _notify() { | |
98 assert(!sealed); | |
99 sealed = true; | |
100 for (final summary in summaries.getValues()) { | |
101 summary.notify(); | |
102 } | |
103 } | |
104 } | |
OLD | NEW |