OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dartino 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.md file. | |
4 | |
5 // Should become auto-generated. | |
6 | |
7 library todomvc_presenter_model; | |
8 | |
9 import 'todomvc_service.dart'; | |
10 | |
11 /* | |
12 The presentation model should be generated by a terse description. | |
13 For example, the presenter model for this sample might have been declared as: | |
14 | |
15 struct Immutable { | |
16 union { | |
17 Atom; | |
18 Cons; | |
19 } | |
20 } | |
21 | |
22 struct Atom { | |
23 union { | |
24 void nil; | |
25 int32 num; | |
26 bool truth; | |
27 String str; | |
28 } | |
29 } | |
30 | |
31 struct Cons { | |
32 Immutable fst; | |
33 Immutable snd; | |
34 } | |
35 | |
36 service TodoListPresenter { | |
37 void Create(String); | |
38 void Delete(Int32); | |
39 void Complete(Int32); | |
40 void Clear(); | |
41 } | |
42 | |
43 From the description we would generate dart classes and their path descriptors | |
44 for each struct together with the diff algorithm. For services we would create | |
45 command descriptors. All of these would come with serialization support for | |
46 wire transfer. | |
47 | |
48 Until then, all of the above is implemented below. | |
49 | |
50 */ | |
51 | |
52 // Tracing to ease debugging on dartino... | |
53 bool TRACE = false; | |
54 void trace(obj) { if (TRACE) print(" $obj"); } | |
55 | |
56 // Some constants. | |
57 | |
58 const TAG_CONS_FST = 0; | |
59 const TAG_CONS_SND = 1; | |
60 const TAG_CONS_DELETE_EVENT = 2; | |
61 const TAG_CONS_COMPLETE_EVENT = 3; | |
62 const TAG_CONS_UNCOMPLETE_EVENT = 4; | |
63 | |
64 // The event manager maintains a mapping from the active handler ids transferred | |
65 // as part of the presentation graph to the handler callbacks. | |
66 class EventManager { | |
67 Map<int, EventHandler> _handlers = new Map(); | |
68 | |
69 int _lfsr = 0xABBA; | |
70 | |
71 int get _next { | |
72 // 16 bits with taps 16, 14, 13, 11. | |
73 int lfsr = _lfsr; | |
74 int bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1; | |
75 lfsr = (lfsr >> 1) | (bit << 15); | |
76 _lfsr = lfsr; | |
77 return lfsr; | |
78 } | |
79 | |
80 int register(EventHandler handler) { | |
81 if (handler == null) return 0; | |
82 if (handler.allocated) { | |
83 // After event manager reset we must re-register event handlers. | |
84 int id = handler.id; | |
85 EventHandler registered = _handlers[id]; | |
86 if (registered == null) { | |
87 _handlers[id] = handler; | |
88 } else if (registered != handler) { | |
89 print("ERROR: attempt to re-register event handler: $id"); | |
90 } | |
91 return id; | |
92 } | |
93 int id = _next; | |
94 // TODO(zerny): Do something clever once completing the lfsr sequence. | |
95 while (_handlers.containsKey(id)) id = _next; | |
96 trace("registered event-handler id: $id"); | |
97 _handlers[id] = handler; | |
98 handler.allocate(id); | |
99 return id; | |
100 } | |
101 | |
102 void unregister(EventHandler handler) { | |
103 if (handler == null) return; | |
104 // TODO(zerny): replace by assert. | |
105 int id = handler.id; | |
106 if (!handler.allocated) { | |
107 print("ERROR: attempt to unregister unallocated event handler: $id"); | |
108 return; | |
109 } | |
110 trace("unregistered event-handler id: $id"); | |
111 assert(_handlers[id] == handler); | |
112 handler.delete(); | |
113 _handlers.remove(id); | |
114 } | |
115 | |
116 void call(int id) { | |
117 assert(id > 0); | |
118 EventHandler handler = _handlers[id]; | |
119 if (handler == null) { | |
120 print("ERROR: attempt to call non-exisiting event handler: $id"); | |
121 return; | |
122 } | |
123 handler.call(); | |
124 } | |
125 | |
126 void clear() { | |
127 _handlers.clear(); | |
128 } | |
129 } | |
130 | |
131 // Immutable model for the presentation (reused as a mutable mirror on the | |
132 // "host" side). (just sexp to simulate a "rich structure" for now) | |
133 | |
134 abstract class Immutable { | |
135 void diff(Immutable other, Path path, MyPatchSet patches); | |
136 void serialize(NodeBuilder builder, EventManager events); | |
137 void unregisterEvents(EventManager events); | |
138 } | |
139 | |
140 class EventHandler extends Immutable { | |
141 final Function _handler; | |
142 int _id = 0; | |
143 | |
144 EventHandler(this._handler); | |
145 | |
146 int get id => _id; | |
147 bool get allocated => _id > 0; | |
148 bool get deleted => _id < 0; | |
149 | |
150 void call() { | |
151 assert(allocated); | |
152 _handler(); | |
153 } | |
154 | |
155 void allocate(int id) { | |
156 assert(!deleted); | |
157 _id = id; | |
158 } | |
159 | |
160 void delete() { | |
161 _id = -1; | |
162 } | |
163 | |
164 static void staticDiff(EventHandler self, | |
165 EventHandler other, | |
166 Path path, | |
167 MyPatchSet patches) { | |
168 if (identical(self, other)) return; | |
169 // TODO(zerny): Do we want to consider another definition of equality here? | |
170 // Currently only the same physically allocated event-handler object is | |
171 // considered "the same", but we could refine that to be if the _handler | |
172 // object is "the same" (which it typically won't be because of closure | |
173 // allocation). Also, if we do so, we need to make sure that the allocated | |
174 // event ids are updated to be the same too, otherwise we could end up with | |
175 // an unallocated event handler being referenced from the binding layer. | |
176 patches.add(path, self, other); | |
177 } | |
178 | |
179 void diff(Immutable other, Path path, MyPatchSet patches) { | |
180 EventHandler.staticDiff(this, other, path, patches); | |
181 } | |
182 | |
183 // TODO(zerny): Event handler fields should not be encoded in Node. | |
184 void serialize(NodeBuilder builder, EventManager events) { | |
185 builder.num = events.register(this); | |
186 } | |
187 | |
188 void unregisterEvents(EventManager events) { | |
189 events.unregister(this); | |
190 } | |
191 } | |
192 | |
193 abstract class Atom extends Immutable { | |
194 final value; | |
195 Atom(this.value) { | |
196 trace("Allocating Atom"); | |
197 } | |
198 String toString() => "value($value)"; | |
199 | |
200 void diff(Immutable other, Path path, MyPatchSet patches) { | |
201 trace("Atom::diff $this ~=~ $other"); | |
202 if (other is Atom && value == other.value) | |
203 return; | |
204 patches.add(path, this, other); | |
205 } | |
206 | |
207 void unregisterEvents(EventManager events) { } | |
208 } | |
209 | |
210 class Cons extends Immutable { | |
211 final Immutable fst, snd; | |
212 final EventHandler deleteEvent, completeEvent, uncompleteEvent; | |
213 | |
214 Cons(this.fst, this.snd, | |
215 [ this.deleteEvent, | |
216 this.completeEvent, | |
217 this.uncompleteEvent ]) { | |
218 trace("Allocating Cons"); | |
219 } | |
220 | |
221 String toString() => "($fst . $snd)"; | |
222 | |
223 void diff(Immutable other, Path path, MyPatchSet patches) { | |
224 if (identical(this, other)) return; | |
225 if (other is! Cons) { | |
226 patches.add(path, this, other); | |
227 return; | |
228 } | |
229 Cons otherCons = other; | |
230 fst.diff(otherCons.fst, new ConsFst(path), patches); | |
231 snd.diff(otherCons.snd, new ConsSnd(path), patches); | |
232 EventHandler.staticDiff( | |
233 deleteEvent, | |
234 otherCons.deleteEvent, | |
235 new ConsDeleteEvent(path), patches); | |
236 EventHandler.staticDiff( | |
237 completeEvent, | |
238 otherCons.completeEvent, | |
239 new ConsCompleteEvent(path), patches); | |
240 EventHandler.staticDiff( | |
241 uncompleteEvent, | |
242 otherCons.uncompleteEvent, | |
243 new ConsUncompleteEvent(path), patches); | |
244 } | |
245 | |
246 void serialize(NodeBuilder builder, EventManager events) { | |
247 trace("Cons::serialize: $this"); | |
248 ConsBuilder cons = builder.initCons(); | |
249 cons.deleteEvent = events.register(deleteEvent); | |
250 cons.completeEvent = events.register(completeEvent); | |
251 cons.uncompleteEvent = events.register(uncompleteEvent); | |
252 fst.serialize(cons.initFst(), events); | |
253 snd.serialize(cons.initSnd(), events); | |
254 } | |
255 | |
256 void unregisterEvents(EventManager events) { | |
257 events.unregister(deleteEvent); | |
258 events.unregister(completeEvent); | |
259 events.unregister(uncompleteEvent); | |
260 fst.unregisterEvents(events); | |
261 snd.unregisterEvents(events); | |
262 } | |
263 } | |
264 | |
265 class Nil extends Atom { | |
266 Nil() : super(null); | |
267 void serialize(NodeBuilder builder, EventManager events) { | |
268 trace("Nil::serialize"); | |
269 builder.setNil(); | |
270 } | |
271 } | |
272 | |
273 class Bool extends Atom { | |
274 Bool(bool value) : super(value); | |
275 void serialize(NodeBuilder builder, EventManager events) { | |
276 trace("Bool::serialize: $this"); | |
277 builder.truth = value; | |
278 } | |
279 } | |
280 | |
281 class Num extends Atom { | |
282 Num(num value) : super(value); | |
283 void serialize(NodeBuilder builder, EventManager events) { | |
284 trace("Num::serialize: $this"); | |
285 builder.num = value; | |
286 } | |
287 } | |
288 | |
289 class Str extends Atom { | |
290 Str(String value) : super(value); | |
291 void serialize(NodeBuilder builder, EventManager events) { | |
292 trace("Str::serialize: $this"); | |
293 builder.str = value; | |
294 } | |
295 } | |
296 | |
297 | |
298 // Path in the immutable model. (We probably won't need an actual representation | |
299 // of these. We could just construct the serialized form on the fly). | |
300 | |
301 // Note that (serialize) reverses the path description. Ie, we construct the | |
302 // path 'inside-out' and read it 'outside-in' on the host side. | |
303 | |
304 abstract class Path { | |
305 final Path parent; | |
306 Path(this.parent); | |
307 int get tag; | |
308 | |
309 static void serialize(Path inner, PatchBuilder builder) { | |
310 trace("Path::serialize: path($inner)"); | |
311 int length = 0; | |
312 for (Path current = inner; current != null; current = current.parent) { | |
313 ++length; | |
314 } | |
315 var out = builder.initPath(length); | |
316 for (Path current = inner; current != null; current = current.parent) { | |
317 out[--length] = current.tag; | |
318 } | |
319 } | |
320 | |
321 String toString() => "$parent;$tag"; | |
322 } | |
323 | |
324 class ConsFst extends Path { | |
325 ConsFst(Path parent) : super(parent); | |
326 int get tag => TAG_CONS_FST; | |
327 } | |
328 | |
329 class ConsSnd extends Path { | |
330 ConsSnd(Path parent) : super(parent); | |
331 int get tag => TAG_CONS_SND; | |
332 } | |
333 | |
334 class ConsDeleteEvent extends Path { | |
335 ConsDeleteEvent(Path parent) : super(parent); | |
336 int get tag => TAG_CONS_DELETE_EVENT; | |
337 } | |
338 | |
339 class ConsCompleteEvent extends Path { | |
340 ConsCompleteEvent(Path parent) : super(parent); | |
341 int get tag => TAG_CONS_COMPLETE_EVENT; | |
342 } | |
343 | |
344 class ConsUncompleteEvent extends Path { | |
345 ConsUncompleteEvent(Path parent) : super(parent); | |
346 int get tag => TAG_CONS_UNCOMPLETE_EVENT; | |
347 } | |
348 | |
349 // Patch description | |
350 | |
351 class MyPatchSet { | |
352 List<Patch> patches = new List<Patch>(); | |
353 add(Path path, Immutable content, Immutable oldContent) => | |
354 patches.add(new Patch(path, content, oldContent)); | |
355 | |
356 void serialize(PatchSetBuilder builder, EventManager events) { | |
357 var length = patches.length; | |
358 trace("MyPatchSet::serialize: length($length)"); | |
359 var out = builder.initPatches(patches.length); | |
360 for (int i = 0; i < length; ++i) { | |
361 patches[i].serialize(out[i], events); | |
362 } | |
363 } | |
364 } | |
365 | |
366 class Patch { | |
367 Path path; | |
368 Immutable content; | |
369 Immutable oldContent; | |
370 Patch(this.path, this.content, this.oldContent); | |
371 | |
372 void serialize(PatchBuilder builder, EventManager events) { | |
373 trace("Patch::serialize"); | |
374 oldContent.unregisterEvents(events); | |
375 Path.serialize(path, builder); | |
376 content.serialize(builder.initContent(), events); | |
377 } | |
378 } | |
OLD | NEW |