| 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 |