alchemymvc 1.3.9 → 1.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/app/conduit/loopback_conduit.js +26 -0
- package/lib/app/conduit/socket_conduit.js +41 -6
- package/lib/app/controller/alchemy_info_controller.js +11 -0
- package/lib/app/helper/router_helper.js +50 -4
- package/lib/app/helper/socket_helper.js +53 -9
- package/lib/app/helper/syncable.js +965 -0
- package/lib/app/helper_model/document.js +35 -5
- package/lib/app/routes.js +3 -1
- package/lib/class/conduit.js +41 -34
- package/lib/class/route.js +3 -3
- package/lib/class/session.js +24 -8
- package/lib/class/session_scene.js +95 -0
- package/lib/core/client_alchemy.js +9 -4
- package/lib/init/alchemy.js +111 -2
- package/package.json +3 -3
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
const SESSION_KEY = 'Syncables',
|
|
2
|
+
UPDATE_ID = Symbol('update_id'),
|
|
3
|
+
CLIENT_MAP = new Classes.WeakValueMap(),
|
|
4
|
+
QUEUE_CALLBACKS = Symbol('queue_callbacks'),
|
|
5
|
+
QUEUE_CALLBACK_ID = Symbol('queue_callback_id');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The Syncable class
|
|
9
|
+
*
|
|
10
|
+
* @constructor
|
|
11
|
+
*
|
|
12
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
13
|
+
* @since 1.3.10
|
|
14
|
+
* @version 1.3.10
|
|
15
|
+
*
|
|
16
|
+
* @param {String} type
|
|
17
|
+
*/
|
|
18
|
+
const Syncable = Function.inherits('Alchemy.Base', function Syncable(type) {
|
|
19
|
+
|
|
20
|
+
if (!type) {
|
|
21
|
+
throw new Error('Each Syncable must have a type');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.root = this;
|
|
25
|
+
this.type = type;
|
|
26
|
+
this.log = [];
|
|
27
|
+
this.counter = 0;
|
|
28
|
+
|
|
29
|
+
this.queues = new Map();
|
|
30
|
+
|
|
31
|
+
if (Blast.isNode) {
|
|
32
|
+
// Only used on the server
|
|
33
|
+
this.s2c_links = new Map();
|
|
34
|
+
} else {
|
|
35
|
+
// Only used on the client
|
|
36
|
+
this.c2s_link = null;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (Blast.isNode) {
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Handle an incoming linkup
|
|
44
|
+
*
|
|
45
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
46
|
+
* @since 1.3.10
|
|
47
|
+
* @version 1.3.10
|
|
48
|
+
*
|
|
49
|
+
* @param {Conduit}
|
|
50
|
+
* @param {Linkup}
|
|
51
|
+
* @param {Object}
|
|
52
|
+
*/
|
|
53
|
+
Syncable.setStatic(function handleLink(conduit, linkup, config) {
|
|
54
|
+
|
|
55
|
+
let syncables = conduit.session(SESSION_KEY),
|
|
56
|
+
syncable,
|
|
57
|
+
error_msg;
|
|
58
|
+
|
|
59
|
+
if (!syncables) {
|
|
60
|
+
error_msg = 'No syncables found';
|
|
61
|
+
} else {
|
|
62
|
+
|
|
63
|
+
let type_map = syncables.get(config.type);
|
|
64
|
+
|
|
65
|
+
if (!type_map) {
|
|
66
|
+
error_msg = 'No syncables found for type: ' + config.type;
|
|
67
|
+
} else {
|
|
68
|
+
syncable = type_map.get(config.id);
|
|
69
|
+
|
|
70
|
+
if (!syncable) {
|
|
71
|
+
error_msg = 'No syncable found for id: ' + config.id;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (error_msg) {
|
|
77
|
+
let err = new Error(error_msg);
|
|
78
|
+
console.log('ERROR:', err);
|
|
79
|
+
linkup.emit('error', err);
|
|
80
|
+
linkup.destroy();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
syncable.attachClient(conduit, linkup, config);
|
|
85
|
+
}, false);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Attach a client
|
|
89
|
+
*
|
|
90
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
91
|
+
* @since 1.3.10
|
|
92
|
+
* @version 1.3.10
|
|
93
|
+
*/
|
|
94
|
+
Syncable.setMethod(function attachClient(scene_id, linkup, config) {
|
|
95
|
+
|
|
96
|
+
if (!scene_id) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!this.s2c_links) {
|
|
101
|
+
this.s2c_links = new Map();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof scene_id == 'object') {
|
|
105
|
+
scene_id = scene_id.scene_id;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!scene_id) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.s2c_links.set(scene_id, linkup);
|
|
113
|
+
|
|
114
|
+
linkup.syncable_version = config.version || 0;
|
|
115
|
+
|
|
116
|
+
linkup.on('destroyed', () => {
|
|
117
|
+
this.s2c_links.delete(scene_id);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
linkup.on('upstream-method', async (args, responder) => {
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Get the value
|
|
124
|
+
let result = await this.handleUpstreamMethodRequest(args[0], args[1]);
|
|
125
|
+
|
|
126
|
+
// Make sure it's ready for the client-side
|
|
127
|
+
result = JSON.clone(result, 'toHawkejs');
|
|
128
|
+
|
|
129
|
+
// Send it to the client
|
|
130
|
+
responder(null, result);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
responder(err);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Send any updates that happened before the linkup was created
|
|
137
|
+
this.sendUpdateToLink(linkup);
|
|
138
|
+
|
|
139
|
+
this.emit('ready');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Handle an upstream method request
|
|
144
|
+
*
|
|
145
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
146
|
+
* @since 1.3.10
|
|
147
|
+
* @version 1.3.10
|
|
148
|
+
*
|
|
149
|
+
* @param {String} name
|
|
150
|
+
* @param {Array} args
|
|
151
|
+
*/
|
|
152
|
+
Syncable.setMethod(function handleUpstreamMethodRequest(name, args) {
|
|
153
|
+
|
|
154
|
+
let method = this[name];
|
|
155
|
+
|
|
156
|
+
if (!method) {
|
|
157
|
+
throw new Error('No method found with name: ' + name);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!method.is_syncable_upstream) {
|
|
161
|
+
throw new Error('Method is not syncable upstream: ' + name);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return method.apply(this, args);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Register a client by one of their conduits.
|
|
169
|
+
* Only clients that are registered can be synced.
|
|
170
|
+
*
|
|
171
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
172
|
+
* @since 1.3.10
|
|
173
|
+
* @version 1.3.10
|
|
174
|
+
*
|
|
175
|
+
* @type {Conduit}
|
|
176
|
+
*/
|
|
177
|
+
Syncable.setMethod(function registerClient(conduit) {
|
|
178
|
+
|
|
179
|
+
if (!conduit) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let syncables = conduit.session(SESSION_KEY);
|
|
184
|
+
|
|
185
|
+
if (!syncables) {
|
|
186
|
+
syncables = new Map();
|
|
187
|
+
conduit.session(SESSION_KEY, syncables);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let type_map = syncables.get(this.type);
|
|
191
|
+
|
|
192
|
+
if (!type_map) {
|
|
193
|
+
type_map = new Map();
|
|
194
|
+
syncables.set(this.type, type_map);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
type_map.set(this.id, this);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (Blast.isBrowser) {
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Start the sync link from the browser to the server
|
|
205
|
+
*
|
|
206
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
207
|
+
* @since 1.3.10
|
|
208
|
+
* @version 1.3.10
|
|
209
|
+
*/
|
|
210
|
+
Syncable.setMethod(function startSyncLink() {
|
|
211
|
+
|
|
212
|
+
if (typeof hawkejs == 'undefined' || !hawkejs.scene) {
|
|
213
|
+
Blast.setImmediate(this.startSyncLink.bind(this));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
alchemy.enableWebsockets();
|
|
218
|
+
|
|
219
|
+
let data = {
|
|
220
|
+
version : this.version,
|
|
221
|
+
type : this.type,
|
|
222
|
+
id : this.id,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
let link = this.c2s_link = alchemy.linkup('syncablelink', data, () => {
|
|
226
|
+
this.emit('ready');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
link.on('process_updates', (data) => {
|
|
230
|
+
|
|
231
|
+
for (let update of data.updates) {
|
|
232
|
+
this.processUpdate(update);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.version = data.version;
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Add a syncable method.
|
|
242
|
+
* The method itself should probably NOT trigger changes
|
|
243
|
+
*
|
|
244
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
245
|
+
* @since 1.3.10
|
|
246
|
+
* @version 1.3.10
|
|
247
|
+
*
|
|
248
|
+
* @param {Object} data
|
|
249
|
+
*
|
|
250
|
+
* @return {Syncable}
|
|
251
|
+
*/
|
|
252
|
+
Syncable.setStatic(function setSyncMethod(types, method) {
|
|
253
|
+
return this.setHandledMethod(types, method, function handler(method, args) {
|
|
254
|
+
|
|
255
|
+
let result = method.apply(this, args);
|
|
256
|
+
|
|
257
|
+
if (this.is_server) {
|
|
258
|
+
this.addLog('call', [method.name, args]);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return result;
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Add a method that fetches info from the server.
|
|
267
|
+
* The response is always a promise.
|
|
268
|
+
*
|
|
269
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
270
|
+
* @since 1.3.10
|
|
271
|
+
* @version 1.3.10
|
|
272
|
+
*
|
|
273
|
+
* @param {Object} data
|
|
274
|
+
*
|
|
275
|
+
* @return {Syncable}
|
|
276
|
+
*/
|
|
277
|
+
Syncable.setStatic(function setUpstreamMethod(types, method) {
|
|
278
|
+
let result = this.setHandledMethod(types, method, function handler(method, args) {
|
|
279
|
+
|
|
280
|
+
let result;
|
|
281
|
+
|
|
282
|
+
if (this.is_server) {
|
|
283
|
+
result = method.apply(this, args);
|
|
284
|
+
} else {
|
|
285
|
+
|
|
286
|
+
let pledge = new Pledge();
|
|
287
|
+
|
|
288
|
+
let bomb = Function.timebomb(30*1000, (err) => {
|
|
289
|
+
pledge.reject(err);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
Pledge.done(this.c2s_link.demand('upstream-method', [method.name, args]), (err, result) => {
|
|
293
|
+
|
|
294
|
+
bomb.defuse();
|
|
295
|
+
|
|
296
|
+
if (err) {
|
|
297
|
+
pledge.reject(err);
|
|
298
|
+
} else {
|
|
299
|
+
pledge.resolve(result);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
result = pledge;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return result;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
result.is_syncable_upstream = true;
|
|
310
|
+
|
|
311
|
+
return result;
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Add a method that may or may not use types
|
|
316
|
+
*
|
|
317
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
318
|
+
* @since 1.3.10
|
|
319
|
+
* @version 1.3.10
|
|
320
|
+
*
|
|
321
|
+
* @param {Array} types The optional types of the method
|
|
322
|
+
* @param {Function} method The main method implementation
|
|
323
|
+
* @param {Function} handler The handler
|
|
324
|
+
*
|
|
325
|
+
* @return {Syncable}
|
|
326
|
+
*/
|
|
327
|
+
Syncable.setStatic(function setHandledMethod(types, method, handler) {
|
|
328
|
+
|
|
329
|
+
let result;
|
|
330
|
+
|
|
331
|
+
if (typeof types == 'function') {
|
|
332
|
+
method = types;
|
|
333
|
+
types = null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function director(...args) {
|
|
337
|
+
return handler.call(this, method, args);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (types) {
|
|
341
|
+
result = this.setTypedMethod(types, method.name, director);
|
|
342
|
+
} else {
|
|
343
|
+
result = this.setMethod(method.name, director);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return result;
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Add a property
|
|
351
|
+
*
|
|
352
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
353
|
+
* @since 1.3.10
|
|
354
|
+
* @version 1.3.10
|
|
355
|
+
*
|
|
356
|
+
* @param {String} name
|
|
357
|
+
* @param {Object} options
|
|
358
|
+
*/
|
|
359
|
+
Syncable.setStatic(function setStateProperty(name, options) {
|
|
360
|
+
|
|
361
|
+
if (!options) {
|
|
362
|
+
options = {};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let has_default = !!options.default,
|
|
366
|
+
allow_set;
|
|
367
|
+
|
|
368
|
+
if (Blast.isNode) {
|
|
369
|
+
allow_set = options.allow_server_set ?? true;
|
|
370
|
+
} else {
|
|
371
|
+
allow_set = options.allow_client_set ?? false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let getter;
|
|
375
|
+
|
|
376
|
+
if (has_default) {
|
|
377
|
+
let has_default_function = typeof options.default == 'function';
|
|
378
|
+
|
|
379
|
+
if (has_default_function) {
|
|
380
|
+
let default_function = options.default;
|
|
381
|
+
|
|
382
|
+
getter = function getter() {
|
|
383
|
+
let result = this.state[name];
|
|
384
|
+
|
|
385
|
+
if (result == null) {
|
|
386
|
+
this.state[name] = result = default_function.call(this);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return result;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
} else {
|
|
393
|
+
let default_value = options.default;
|
|
394
|
+
|
|
395
|
+
getter = function getter() {
|
|
396
|
+
|
|
397
|
+
let result = this.state[name];
|
|
398
|
+
|
|
399
|
+
if (result == null) {
|
|
400
|
+
this.state[name] = result = default_value;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return result;
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
} else {
|
|
408
|
+
getter = function getter() {
|
|
409
|
+
return this.state[name];
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (allow_set) {
|
|
414
|
+
this.setProperty(name, getter, function setValue(value) {
|
|
415
|
+
this.setProperty(name, value);
|
|
416
|
+
});
|
|
417
|
+
} else {
|
|
418
|
+
this.setProperty(name, getter);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Undry this value
|
|
424
|
+
*
|
|
425
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
426
|
+
* @since 1.3.10
|
|
427
|
+
* @version 1.3.10
|
|
428
|
+
*
|
|
429
|
+
* @param {Object} data
|
|
430
|
+
*
|
|
431
|
+
* @return {Syncable}
|
|
432
|
+
*/
|
|
433
|
+
Syncable.setStatic(function unDry(data) {
|
|
434
|
+
|
|
435
|
+
let result;
|
|
436
|
+
|
|
437
|
+
// Try to reuse the same instance on the client side
|
|
438
|
+
if (Blast.isBrowser) {
|
|
439
|
+
result = CLIENT_MAP.get(data.id);
|
|
440
|
+
|
|
441
|
+
if (result) {
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
let clone = JSON.clone(data);
|
|
447
|
+
|
|
448
|
+
result = new this(clone.type);
|
|
449
|
+
|
|
450
|
+
result.id = clone.id;
|
|
451
|
+
result.state = clone.state;
|
|
452
|
+
result.version = clone.version;
|
|
453
|
+
result.queues = clone.queues;
|
|
454
|
+
|
|
455
|
+
if (Blast.isBrowser) {
|
|
456
|
+
result.startSyncLink();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return result;
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Is this the server instance?
|
|
464
|
+
*
|
|
465
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
466
|
+
* @since 1.3.10
|
|
467
|
+
* @version 1.3.10
|
|
468
|
+
*
|
|
469
|
+
* @type {Conduit}
|
|
470
|
+
*/
|
|
471
|
+
Syncable.setProperty(function is_server() {
|
|
472
|
+
return Blast.isNode;
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Enforce the ID property
|
|
477
|
+
*
|
|
478
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
479
|
+
* @since 1.3.10
|
|
480
|
+
* @version 1.3.10
|
|
481
|
+
*
|
|
482
|
+
* @type {String}
|
|
483
|
+
*/
|
|
484
|
+
Syncable.enforceProperty(function id(new_value) {
|
|
485
|
+
|
|
486
|
+
if (!new_value) {
|
|
487
|
+
new_value = Crypto.randomHex(16);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Remember this instance for later
|
|
491
|
+
if (Blast.isBrowser) {
|
|
492
|
+
CLIENT_MAP.set(new_value, this);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return new_value;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Enforce the queues property
|
|
500
|
+
*
|
|
501
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
502
|
+
* @since 1.3.10
|
|
503
|
+
* @version 1.3.10
|
|
504
|
+
*
|
|
505
|
+
* @type {Map}
|
|
506
|
+
*/
|
|
507
|
+
Syncable.enforceProperty(function queues(new_value) {
|
|
508
|
+
|
|
509
|
+
if (!new_value) {
|
|
510
|
+
new_value = new Map();
|
|
511
|
+
} else {
|
|
512
|
+
|
|
513
|
+
// Loop over every queue entry
|
|
514
|
+
for (let [name, queue] of new_value) {
|
|
515
|
+
|
|
516
|
+
let listeners = [];
|
|
517
|
+
|
|
518
|
+
if (queue.listeners) {
|
|
519
|
+
for (let listener of queue.listeners) {
|
|
520
|
+
if (listener) {
|
|
521
|
+
listeners.push(listener);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
queue.listeners = listeners;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return new_value;
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Enforce the state property
|
|
535
|
+
*
|
|
536
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
537
|
+
* @since 1.3.10
|
|
538
|
+
* @version 1.3.10
|
|
539
|
+
*
|
|
540
|
+
* @type {String}
|
|
541
|
+
*/
|
|
542
|
+
Syncable.enforceProperty(function state(new_value) {
|
|
543
|
+
|
|
544
|
+
if (!new_value) {
|
|
545
|
+
new_value = {};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return new_value;
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Enforce the version property
|
|
553
|
+
*
|
|
554
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
555
|
+
* @since 1.3.10
|
|
556
|
+
* @version 1.3.10
|
|
557
|
+
*
|
|
558
|
+
* @type {String}
|
|
559
|
+
*/
|
|
560
|
+
Syncable.enforceProperty(function version(new_value) {
|
|
561
|
+
|
|
562
|
+
if (!new_value) {
|
|
563
|
+
new_value = 0;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return new_value;
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Clone for hawkejs
|
|
571
|
+
*
|
|
572
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
573
|
+
* @since 1.3.10
|
|
574
|
+
* @version 1.3.10
|
|
575
|
+
*/
|
|
576
|
+
Syncable.setMethod(function toHawkejs() {
|
|
577
|
+
return this.constructor.unDry(this.toDry().value);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Serialize this syncable
|
|
582
|
+
*
|
|
583
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
584
|
+
* @since 1.3.10
|
|
585
|
+
* @version 1.3.10
|
|
586
|
+
*/
|
|
587
|
+
Syncable.setMethod(function toDry() {
|
|
588
|
+
|
|
589
|
+
let queues = new Map();
|
|
590
|
+
|
|
591
|
+
for (let [name, queue] of this.queues) {
|
|
592
|
+
|
|
593
|
+
// Keep everything except the `listeners`
|
|
594
|
+
let entry = {
|
|
595
|
+
name : name,
|
|
596
|
+
listeners : [],
|
|
597
|
+
messages : queue.messages,
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
queues.set(name, entry);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
let result = {
|
|
604
|
+
version : this.version,
|
|
605
|
+
queues : queues,
|
|
606
|
+
state : this.state,
|
|
607
|
+
type : this.type,
|
|
608
|
+
id : this.id,
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
return {value: result};
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Process an update
|
|
616
|
+
*
|
|
617
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
618
|
+
* @since 1.3.10
|
|
619
|
+
* @version 1.3.10
|
|
620
|
+
*/
|
|
621
|
+
Syncable.setMethod(function processUpdate(update) {
|
|
622
|
+
|
|
623
|
+
let type = update.type,
|
|
624
|
+
args = update.args;
|
|
625
|
+
|
|
626
|
+
if (type == 'set') {
|
|
627
|
+
this.setProperty(...args);
|
|
628
|
+
} else if (type == 'call') {
|
|
629
|
+
let name = args[0],
|
|
630
|
+
method_args = args[1];
|
|
631
|
+
|
|
632
|
+
this[name](...method_args);
|
|
633
|
+
} else if (type == 'push_queue') {
|
|
634
|
+
let name = args[0],
|
|
635
|
+
method_args = args[1];
|
|
636
|
+
|
|
637
|
+
this.pushQueue(name, ...method_args);
|
|
638
|
+
} else if (type == 'clear_queue') {
|
|
639
|
+
let name = args[0];
|
|
640
|
+
this.clearQueue(name);
|
|
641
|
+
} else {
|
|
642
|
+
throw new Error('Unknown update type: ' + type);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Send the actual update
|
|
648
|
+
*
|
|
649
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
650
|
+
* @since 1.3.10
|
|
651
|
+
* @version 1.3.10
|
|
652
|
+
*/
|
|
653
|
+
Syncable.setMethod(function sendUpdates() {
|
|
654
|
+
|
|
655
|
+
if (this[UPDATE_ID]) {
|
|
656
|
+
clearTimeout(this[UPDATE_ID]);
|
|
657
|
+
this[UPDATE_ID] = null;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
for (let linkup of this.s2c_links.values()) {
|
|
661
|
+
this.sendUpdateToLink(linkup);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Emit a change event for a certain property
|
|
667
|
+
*
|
|
668
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
669
|
+
* @since 1.3.10
|
|
670
|
+
* @version 1.3.10
|
|
671
|
+
*
|
|
672
|
+
* @param {String} property
|
|
673
|
+
*/
|
|
674
|
+
Syncable.setMethod(function emitPropertyChange(property) {
|
|
675
|
+
let value = this[property];
|
|
676
|
+
this.emit('property_change_' + property, value, null);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Listen for a change event for a certain property
|
|
681
|
+
*
|
|
682
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
683
|
+
* @since 1.3.10
|
|
684
|
+
* @version 1.3.10
|
|
685
|
+
*
|
|
686
|
+
* @param {String} property
|
|
687
|
+
* @param {Function} callback
|
|
688
|
+
*/
|
|
689
|
+
Syncable.setMethod(function watchProperty(property, callback) {
|
|
690
|
+
this.on('property_change_' + property, callback);
|
|
691
|
+
let value = this[property];
|
|
692
|
+
callback(value);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Watch a queue for changes
|
|
697
|
+
*
|
|
698
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
699
|
+
* @since 1.3.10
|
|
700
|
+
* @version 1.3.10
|
|
701
|
+
*
|
|
702
|
+
* @param {String} name
|
|
703
|
+
* @param {Function} callback
|
|
704
|
+
*/
|
|
705
|
+
Syncable.setMethod(function watchQueue(name, callback) {
|
|
706
|
+
|
|
707
|
+
let queue = this.queues.get(name);
|
|
708
|
+
|
|
709
|
+
if (!queue) {
|
|
710
|
+
queue = {
|
|
711
|
+
name,
|
|
712
|
+
listeners: [],
|
|
713
|
+
messages : [],
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
this.queues.set(name, queue);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
queue.listeners.push(callback);
|
|
720
|
+
|
|
721
|
+
// Drain all the messages
|
|
722
|
+
while (queue.messages.length) {
|
|
723
|
+
this.scheduleQueueCallback(queue.messages.shift(), callback);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Clear all the entries in a queue
|
|
729
|
+
*
|
|
730
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
731
|
+
* @since 1.3.10
|
|
732
|
+
* @version 1.3.10
|
|
733
|
+
*
|
|
734
|
+
* @param {String} name
|
|
735
|
+
*/
|
|
736
|
+
Syncable.setAfterMethod('ready', function clearQueue(name) {
|
|
737
|
+
|
|
738
|
+
let queue = this.queues.get(name);
|
|
739
|
+
|
|
740
|
+
if (queue) {
|
|
741
|
+
queue.messages = [];
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (Blast.isNode) {
|
|
745
|
+
this.addLog('clear_queue', [name]);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Schedule a queue callback
|
|
751
|
+
* (This tries to keep events in different queues still use the same order)
|
|
752
|
+
*
|
|
753
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
754
|
+
* @since 1.3.10
|
|
755
|
+
* @version 1.3.10
|
|
756
|
+
*/
|
|
757
|
+
Syncable.setAfterMethod('ready', function scheduleQueueCallback(config, callback) {
|
|
758
|
+
|
|
759
|
+
let args = config.args,
|
|
760
|
+
counter = config.counter;
|
|
761
|
+
|
|
762
|
+
if (!this[QUEUE_CALLBACKS]) {
|
|
763
|
+
this[QUEUE_CALLBACKS] = [];
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
this[QUEUE_CALLBACKS].push({args, counter, callback});
|
|
767
|
+
|
|
768
|
+
if (this[QUEUE_CALLBACK_ID]) {
|
|
769
|
+
clearTimeout(this[QUEUE_CALLBACK_ID]);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
this[QUEUE_CALLBACK_ID] = setTimeout(() => {
|
|
773
|
+
this[QUEUE_CALLBACK_ID] = null;
|
|
774
|
+
this.processQueueCallbacks();
|
|
775
|
+
}, 10);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Actually do the queued callbacks
|
|
780
|
+
*
|
|
781
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
782
|
+
* @since 1.3.10
|
|
783
|
+
* @version 1.3.10
|
|
784
|
+
*/
|
|
785
|
+
Syncable.setAfterMethod('ready', function processQueueCallbacks() {
|
|
786
|
+
|
|
787
|
+
let callbacks = this[QUEUE_CALLBACKS];
|
|
788
|
+
|
|
789
|
+
if (!callbacks) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
this[QUEUE_CALLBACKS] = [];
|
|
794
|
+
|
|
795
|
+
callbacks.sort((a, b) => {
|
|
796
|
+
return a.counter - b.counter;
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
for (let item of callbacks) {
|
|
800
|
+
item.callback(...item.args);
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Push something to a queue.
|
|
806
|
+
* If there are listeners, they will be called immediately.
|
|
807
|
+
*
|
|
808
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
809
|
+
* @since 1.3.10
|
|
810
|
+
* @version 1.3.10
|
|
811
|
+
*
|
|
812
|
+
* @param {String} name
|
|
813
|
+
*/
|
|
814
|
+
Syncable.setAfterMethod('ready', function pushQueue(name, ...args) {
|
|
815
|
+
|
|
816
|
+
let queue = this.queues.get(name);
|
|
817
|
+
|
|
818
|
+
if (!queue) {
|
|
819
|
+
queue = {
|
|
820
|
+
name,
|
|
821
|
+
listeners: [],
|
|
822
|
+
messages : [],
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
this.queues.set(name, queue);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (queue.listeners.length) {
|
|
829
|
+
for (let listener of queue.listeners) {
|
|
830
|
+
this.scheduleQueueCallback({args, counter: this.counter++}, listener);
|
|
831
|
+
}
|
|
832
|
+
} else if (!Blast.isNode) {
|
|
833
|
+
queue.messages.push({
|
|
834
|
+
counter : this.counter++,
|
|
835
|
+
args
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (Blast.isNode) {
|
|
840
|
+
this.addLog('push_queue', [name, args]);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Send an update to the given link
|
|
846
|
+
*
|
|
847
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
848
|
+
* @since 1.3.10
|
|
849
|
+
* @version 1.3.10
|
|
850
|
+
*/
|
|
851
|
+
Syncable.setMethod(function sendUpdateToLink(link) {
|
|
852
|
+
|
|
853
|
+
let link_version = link.syncable_version;
|
|
854
|
+
|
|
855
|
+
if (link_version >= this.version) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
let updates = [];
|
|
860
|
+
|
|
861
|
+
for (let i = link_version; i < this.version; i++) {
|
|
862
|
+
let entry = this.log[i];
|
|
863
|
+
|
|
864
|
+
if (entry) {
|
|
865
|
+
updates.push(entry);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (updates.length) {
|
|
870
|
+
link.submit('process_updates', {
|
|
871
|
+
updates,
|
|
872
|
+
version: this.version,
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
link.syncable_version = this.version;
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Queue an update to all the listeners
|
|
881
|
+
*
|
|
882
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
883
|
+
* @since 1.3.10
|
|
884
|
+
* @version 1.3.10
|
|
885
|
+
*/
|
|
886
|
+
Syncable.setMethod(function queueUpdate() {
|
|
887
|
+
|
|
888
|
+
let update_id = this[UPDATE_ID];
|
|
889
|
+
|
|
890
|
+
if (update_id) {
|
|
891
|
+
clearTimeout(update_id);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
this[UPDATE_ID] = setTimeout(this.sendUpdates.bind(this), 30);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Add something to the log
|
|
899
|
+
*
|
|
900
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
901
|
+
* @since 1.3.10
|
|
902
|
+
* @version 1.3.10
|
|
903
|
+
*/
|
|
904
|
+
Syncable.setMethod(function _addLog(type, args) {
|
|
905
|
+
|
|
906
|
+
let entry = {
|
|
907
|
+
version : this.version,
|
|
908
|
+
type : type,
|
|
909
|
+
args : args,
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
this.log.push(entry);
|
|
913
|
+
this.queueUpdate();
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Add something to the log and increase the version
|
|
918
|
+
*
|
|
919
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
920
|
+
* @since 1.3.10
|
|
921
|
+
* @version 1.3.10
|
|
922
|
+
*/
|
|
923
|
+
Syncable.setMethod(function addLog(type, args) {
|
|
924
|
+
this.version++;
|
|
925
|
+
this._addLog(type, args);
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Set a property to a specific value
|
|
930
|
+
*
|
|
931
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
932
|
+
* @since 1.3.10
|
|
933
|
+
* @version 1.3.10
|
|
934
|
+
*/
|
|
935
|
+
Syncable.setMethod(function setProperty(key, value) {
|
|
936
|
+
if (this.state[key] !== value) {
|
|
937
|
+
this.state[key] = value;
|
|
938
|
+
|
|
939
|
+
if (this.is_server) {
|
|
940
|
+
this.addLog('set', [key, value]);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
this.emitPropertyChange(key);
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Release the syncable
|
|
949
|
+
* (On your own side)
|
|
950
|
+
*
|
|
951
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
952
|
+
* @since 1.3.10
|
|
953
|
+
* @version 1.3.10
|
|
954
|
+
*/
|
|
955
|
+
Syncable.setMethod(function release() {
|
|
956
|
+
|
|
957
|
+
if (this.c2s_link) {
|
|
958
|
+
this.c2s_link.destroy();
|
|
959
|
+
this.c2s_link = null;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (Blast.isBrowser) {
|
|
963
|
+
CLIENT_MAP.delete(this.id);
|
|
964
|
+
}
|
|
965
|
+
});
|