included connector type
This commit is contained in:
parent
f835a72151
commit
58a479be9b
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,7 +1,13 @@
|
||||
var adaptConnector;
|
||||
var ConnectorClass, adaptConnector;
|
||||
|
||||
ConnectorClass = require("./ConnectorClass");
|
||||
|
||||
adaptConnector = function(connector, engine, HB, execution_listener) {
|
||||
var applyHB, encode_state_vector, getHB, getStateVector, parse_state_vector, send_;
|
||||
var applyHB, encode_state_vector, f, getHB, getStateVector, name, parse_state_vector, send_;
|
||||
for (name in ConnectorClass) {
|
||||
f = ConnectorClass[name];
|
||||
connector[name] = f;
|
||||
}
|
||||
send_ = function(o) {
|
||||
if (o.uid.creator === HB.getUserId() && (typeof o.uid.op_number !== "string")) {
|
||||
return connector.broadcast(o);
|
||||
@ -12,7 +18,7 @@ adaptConnector = function(connector, engine, HB, execution_listener) {
|
||||
}
|
||||
execution_listener.push(send_);
|
||||
encode_state_vector = function(v) {
|
||||
var name, value, _results;
|
||||
var value, _results;
|
||||
_results = [];
|
||||
for (name in v) {
|
||||
value = v[name];
|
||||
@ -55,6 +61,7 @@ adaptConnector = function(connector, engine, HB, execution_listener) {
|
||||
connector.getStateVector = getStateVector;
|
||||
connector.getHB = getHB;
|
||||
connector.applyHB = applyHB;
|
||||
connector.receive_handlers = [];
|
||||
connector.receive_handlers.push(function(sender, op) {
|
||||
if (op.uid.creator !== HB.getUserId()) {
|
||||
return engine.applyOp(op);
|
||||
|
304
build/node/ConnectorClass.js
Normal file
304
build/node/ConnectorClass.js
Normal file
@ -0,0 +1,304 @@
|
||||
module.exports = {
|
||||
init: function(options) {
|
||||
var req;
|
||||
req = (function(_this) {
|
||||
return function(name, choices) {
|
||||
if (options[name] != null) {
|
||||
if ((choices == null) || choices.some(function(c) {
|
||||
return c === options[name];
|
||||
})) {
|
||||
return _this[name] = options[name];
|
||||
} else {
|
||||
throw new Error("You can set the '" + name + "' option to one of the following choices: " + JSON.encode(choices));
|
||||
}
|
||||
} else {
|
||||
throw new Error("You must specify " + name + ", when initializing the Connector!");
|
||||
}
|
||||
};
|
||||
})(this);
|
||||
req("syncMode", ["syncAll", "master-slave"]);
|
||||
req("role", ["master", "slave"]);
|
||||
req("user_id");
|
||||
this.on_user_id_set(this.user_id);
|
||||
if (this.role === "master") {
|
||||
this.syncMode = "syncAll";
|
||||
}
|
||||
this.is_synced = false;
|
||||
this.is_syncing = false;
|
||||
this.connections = {};
|
||||
this.is_bound_to_y = false;
|
||||
this.connections = {};
|
||||
return this.current_sync_target = null;
|
||||
},
|
||||
isRoleMaster: function() {
|
||||
return this.role === "master";
|
||||
},
|
||||
isRoleSlave: function() {
|
||||
return this.role === "slave";
|
||||
},
|
||||
findNewSyncTarget: function() {
|
||||
var c, user, _ref;
|
||||
this.current_sync_target = null;
|
||||
if (this.syncMode === "syncAll") {
|
||||
_ref = this.connections;
|
||||
for (user in _ref) {
|
||||
c = _ref[user];
|
||||
if (!c.is_synced) {
|
||||
this.performSync(user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
userLeft: function(user) {
|
||||
delete this.connections[user];
|
||||
return this.findNewSyncTarget();
|
||||
},
|
||||
userJoined: function(user, role) {
|
||||
if (role == null) {
|
||||
throw new Error("Internal: You must specify the role of the joined user!");
|
||||
}
|
||||
this.connections[user] = {
|
||||
is_synced: false
|
||||
};
|
||||
if ((!this.is_synced) || this.syncMode === "syncAll") {
|
||||
if (this.syncMode === "syncAll") {
|
||||
return this.performSync(user);
|
||||
} else if (role === "master") {
|
||||
return this.performSyncWithMaster(user);
|
||||
}
|
||||
}
|
||||
},
|
||||
whenSynced: function(args) {
|
||||
if (args.constructore === Function) {
|
||||
args = [args];
|
||||
}
|
||||
if (this.is_synced) {
|
||||
return args[0].apply(this, args.slice(1));
|
||||
} else {
|
||||
if (this.compute_when_synced == null) {
|
||||
this.compute_when_synced = [];
|
||||
}
|
||||
return this.compute_when_synced.push(args);
|
||||
}
|
||||
},
|
||||
onReceive: function(f) {
|
||||
return this.receive_handlers.push(f);
|
||||
},
|
||||
|
||||
/*
|
||||
* Broadcast a message to all connected peers.
|
||||
* @param message {Object} The message to broadcast.
|
||||
*
|
||||
broadcast: (message)->
|
||||
throw new Error "You must implement broadcast!"
|
||||
|
||||
*
|
||||
* Send a message to a peer, or set of peers
|
||||
*
|
||||
send: (peer_s, message)->
|
||||
throw new Error "You must implement send!"
|
||||
*/
|
||||
performSync: function(user) {
|
||||
if (this.current_sync_target == null) {
|
||||
this.current_sync_target = user;
|
||||
return this.send(user, {
|
||||
sync_step: "getHB",
|
||||
data: this.getStateVector()
|
||||
});
|
||||
}
|
||||
},
|
||||
performSyncWithMaster: function(user) {
|
||||
var hb, o, _hb, _i, _len;
|
||||
if (!this.is_syncing) {
|
||||
this.current_sync_target = user;
|
||||
this.is_syncing = true;
|
||||
this.send(user, {
|
||||
sync_step: "getHB",
|
||||
send_again: "true",
|
||||
data: []
|
||||
});
|
||||
hb = this.getHB([]).hb;
|
||||
_hb = [];
|
||||
for (_i = 0, _len = hb.length; _i < _len; _i++) {
|
||||
o = hb[_i];
|
||||
_hb.push(o);
|
||||
if (_hb.length > 30) {
|
||||
this.broadcast({
|
||||
sync_step: "applyHB_",
|
||||
data: _hb
|
||||
});
|
||||
_hb = [];
|
||||
}
|
||||
}
|
||||
return this.broadcast({
|
||||
sync_step: "applyHB",
|
||||
data: _hb
|
||||
});
|
||||
}
|
||||
},
|
||||
setStateSynced: function() {
|
||||
var f, _i, _len, _ref;
|
||||
if (!this.is_synced) {
|
||||
this.is_synced = true;
|
||||
_ref = this.compute_when_synced;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
f = _ref[_i];
|
||||
f();
|
||||
}
|
||||
delete this.compute_when_synced;
|
||||
return null;
|
||||
}
|
||||
},
|
||||
receiveMessage: function(sender, res) {
|
||||
var data, f, hb, o, send_again, _hb, _i, _j, _len, _len1, _ref, _results;
|
||||
if (res.sync_step == null) {
|
||||
_ref = this.receive_handlers;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
f = _ref[_i];
|
||||
_results.push(f(sender, res));
|
||||
}
|
||||
return _results;
|
||||
} else {
|
||||
if (res.sync_step === "getHB") {
|
||||
data = this.getHB(res.data);
|
||||
hb = data.hb;
|
||||
_hb = [];
|
||||
for (_j = 0, _len1 = hb.length; _j < _len1; _j++) {
|
||||
o = hb[_j];
|
||||
_hb.push(o);
|
||||
if (_hb.length > 30) {
|
||||
this.send(sender, {
|
||||
sync_step: "applyHB_",
|
||||
data: _hb
|
||||
});
|
||||
_hb = [];
|
||||
}
|
||||
}
|
||||
this.send(sender, {
|
||||
sync_step: "applyHB",
|
||||
data: _hb
|
||||
});
|
||||
if (res.send_again != null) {
|
||||
send_again = (function(_this) {
|
||||
return function(sv) {
|
||||
return function() {
|
||||
hb = _this.getHB(sv).hb;
|
||||
return _this.send(sender, {
|
||||
sync_step: "applyHB",
|
||||
data: hb,
|
||||
sent_again: "true"
|
||||
});
|
||||
};
|
||||
};
|
||||
})(this)(data.state_vector);
|
||||
return setTimeout(send_again, 3000);
|
||||
}
|
||||
} else if (res.sync_step === "applyHB") {
|
||||
this.applyHB(res.data);
|
||||
if ((this.syncMode === "syncAll" || (res.sent_again != null)) && !this.is_synced) {
|
||||
this.setStateSynced();
|
||||
this.connections[sender].is_synced = true;
|
||||
return this.findNewSyncTarget();
|
||||
}
|
||||
} else if (res.sync_step === "applyHB_") {
|
||||
return this.applyHB(res.data);
|
||||
}
|
||||
}
|
||||
},
|
||||
parseMessageFromXml: function(m) {
|
||||
var parse_array, parse_object;
|
||||
parse_array = function(node) {
|
||||
var n, _i, _len, _ref, _results;
|
||||
_ref = node.children;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
n = _ref[_i];
|
||||
if (n.getAttribute("isArray") === "true") {
|
||||
_results.push(parse_array(n));
|
||||
} else {
|
||||
_results.push(parse_object(n));
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
parse_object = function(node) {
|
||||
var int, json, n, name, value, _i, _len, _ref, _ref1;
|
||||
json = {};
|
||||
_ref = node.attrs;
|
||||
for (name in _ref) {
|
||||
value = _ref[name];
|
||||
int = parseInt(value);
|
||||
if (isNaN(int) || ("" + int) !== value) {
|
||||
json[name] = value;
|
||||
} else {
|
||||
json[name] = int;
|
||||
}
|
||||
}
|
||||
_ref1 = node.children;
|
||||
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
|
||||
n = _ref1[_i];
|
||||
name = n.name;
|
||||
if (n.getAttribute("isArray") === "true") {
|
||||
json[name] = parse_array(n);
|
||||
} else {
|
||||
json[name] = parse_object(n);
|
||||
}
|
||||
}
|
||||
return json;
|
||||
};
|
||||
return parse_object(m);
|
||||
},
|
||||
encodeMessageToXml: function(m, json) {
|
||||
var encode_array, encode_object;
|
||||
encode_object = function(m, json) {
|
||||
var name, value;
|
||||
for (name in json) {
|
||||
value = json[name];
|
||||
if (value == null) {
|
||||
|
||||
} else if (value.constructor === Object) {
|
||||
encode_object(m.c(name), value);
|
||||
} else if (value.constructor === Array) {
|
||||
encode_array(m.c(name), value);
|
||||
} else {
|
||||
m.setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
encode_array = function(m, array) {
|
||||
var e, _i, _len;
|
||||
m.setAttribute("isArray", "true");
|
||||
for (_i = 0, _len = array.length; _i < _len; _i++) {
|
||||
e = array[_i];
|
||||
if (e.constructor === Object) {
|
||||
encode_object(m.c("array-element"), e);
|
||||
} else {
|
||||
encode_array(m.c("array-element"), e);
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
if (json.constructor === Object) {
|
||||
return encode_object(m.c("y", {
|
||||
xmlns: "http://y.ninja/connector-stanza"
|
||||
}), json);
|
||||
} else if (json.constructor === Array) {
|
||||
return encode_array(m.c("y", {
|
||||
xmlns: "http://y.ninja/connector-stanza"
|
||||
}), json);
|
||||
} else {
|
||||
throw new Error("I can't encode this json!");
|
||||
}
|
||||
},
|
||||
setIsBoundToY: function() {
|
||||
if (typeof this.on_bound_to_y === "function") {
|
||||
this.on_bound_to_y();
|
||||
}
|
||||
delete this.when_bound_to_y;
|
||||
return this.is_bound_to_y = true;
|
||||
}
|
||||
};
|
@ -30,8 +30,10 @@ HistoryBuffer = (function() {
|
||||
this.buffer[id] = own;
|
||||
delete this.buffer[this.user_id];
|
||||
}
|
||||
this.operation_counter[id] = this.operation_counter[this.user_id];
|
||||
delete this.operation_counter[this.user_id];
|
||||
if (this.operation_counter[this.user_id] != null) {
|
||||
this.operation_counter[id] = this.operation_counter[this.user_id];
|
||||
delete this.operation_counter[this.user_id];
|
||||
}
|
||||
return this.user_id = id;
|
||||
};
|
||||
|
||||
@ -221,7 +223,7 @@ HistoryBuffer = (function() {
|
||||
_results = [];
|
||||
for (user in state_vector) {
|
||||
state = state_vector[user];
|
||||
if ((this.operation_counter[user] == null) || (this.operation_counter[user] < state_vector[user])) {
|
||||
if (((this.operation_counter[user] == null) || (this.operation_counter[user] < state_vector[user])) && (state_vector[user] != null)) {
|
||||
_results.push(this.operation_counter[user] = state_vector[user]);
|
||||
} else {
|
||||
_results.push(void 0);
|
||||
|
@ -187,7 +187,7 @@ module.exports = function(HB) {
|
||||
|
||||
ReplaceManager.prototype.callEventDecorator = function(events) {
|
||||
var event, name, prop, _i, _len, _ref;
|
||||
if (!this.isDeleted()) {
|
||||
if (!(this.isDeleted() || this.getLastOperation().isDeleted())) {
|
||||
for (_i = 0, _len = events.length; _i < _len; _i++) {
|
||||
event = events[_i];
|
||||
_ref = this.event_properties;
|
||||
|
@ -13,14 +13,14 @@ adaptConnector = require("./ConnectorAdapter");
|
||||
createY = function(connector) {
|
||||
var HB, Y, type_manager, types, user_id;
|
||||
user_id = null;
|
||||
if (connector.id != null) {
|
||||
user_id = connector.id;
|
||||
if (connector.user_id != null) {
|
||||
user_id = connector.user_id;
|
||||
} else {
|
||||
user_id = "_temp";
|
||||
connector.onUserIdSet(function(id) {
|
||||
connector.on_user_id_set = function(id) {
|
||||
user_id = id;
|
||||
return HB.resetUserId(id);
|
||||
});
|
||||
};
|
||||
}
|
||||
HB = new HistoryBuffer(user_id);
|
||||
type_manager = json_types_uninitialized(HB);
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -10,6 +10,5 @@
|
||||
</head>
|
||||
<body>
|
||||
<y-test></y-test>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,19 +0,0 @@
|
||||
|
||||
setTimeout(function(){
|
||||
window.y_test = document.querySelector("y-test");
|
||||
|
||||
window.y_test.y.val("stuff",{otherstuff:{nostuff:"this is no stuff"}})
|
||||
setTimeout(function(){
|
||||
var res = y_test.y.val("stuff");
|
||||
if(!(y_test.nostuff === "this is no stuff")){
|
||||
console.log("Deep inherit doesn't work!")
|
||||
}
|
||||
window.y_stuff_property.val = {nostuff: "this is also no stuff"};
|
||||
setTimeout(function(){
|
||||
if(!(y_test.nostuff === "this is also no stuff")){
|
||||
console.log("Element val overwrite doesn't work")
|
||||
}
|
||||
console.log("Everything is fine :)");
|
||||
},500)
|
||||
},500);
|
||||
},3000)
|
@ -1,11 +1,11 @@
|
||||
<link rel="import" href="../../y-object.html">
|
||||
<link rel="import" href="../../../y-connectors/y-xmpp/y-xmpp.html">
|
||||
<link rel="import" href="../../../y-xmpp/build/browser/y-xmpp.html">
|
||||
<link rel="import" href="../../../paper-slider/paper-slider.html">
|
||||
|
||||
<polymer-element name="y-test" attributes="y connector stuff">
|
||||
<template>
|
||||
<h1 id="text" contentEditable> Check this out !</h1>
|
||||
<y-xmpp id="connector" connector={{connector}} room="testy-xmpp-polymer"></y-xmpp>
|
||||
<y-xmpp id="connector" connector={{connector}} room="testy-xmpp-polymer" syncMode="syncAll"></y-xmpp>
|
||||
<y-object connector={{connector}} val={{y}}>
|
||||
<y-property name="slider" val={{slider}}>
|
||||
</y-property>
|
||||
@ -32,6 +32,23 @@
|
||||
}
|
||||
that.y.val("text").bind(that.$.text,that.shadowRoot)
|
||||
})
|
||||
|
||||
// Everything is initialized. Lets test stuff!
|
||||
window.y_test = this;
|
||||
window.y_test.y.val("stuff",{otherstuff:{nostuff:"this is no stuff"}})
|
||||
setTimeout(function(){
|
||||
var res = y_test.y.val("stuff");
|
||||
if(!(y_test.nostuff === "this is no stuff")){
|
||||
console.log("Deep inherit doesn't work!")
|
||||
}
|
||||
window.y_stuff_property.val = {nostuff: "this is also no stuff"};
|
||||
setTimeout(function(){
|
||||
if(!(y_test.nostuff === "this is also no stuff")){
|
||||
console.log("Element val overwrite doesn't work")
|
||||
}
|
||||
console.log("Everything is fine :)");
|
||||
},500)
|
||||
},500);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset=utf-8 />
|
||||
<title>Y Example</title>
|
||||
<script src="../../build/browser/y.js"></script>
|
||||
<script src="../../../y-connectors/y-xmpp/y-xmpp.js"></script>
|
||||
<script src="../../../y-xmpp/y-xmpp.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
connector = new Y.XMPP("testy-xmpp-json2");
|
||||
connector = new Y.XMPP().join("testy-xmpp-json3", {syncMode: "syncAll"});
|
||||
connector.debug = true
|
||||
|
||||
y = new Y(connector);
|
||||
|
@ -1,11 +1,15 @@
|
||||
|
||||
|
||||
ConnectorClass = require "./ConnectorClass"
|
||||
#
|
||||
# @param {Engine} engine The transformation engine
|
||||
# @param {HistoryBuffer} HB
|
||||
# @param {Array<Function>} execution_listener You must ensure that whenever an operation is executed, every function in this Array is called.
|
||||
#
|
||||
adaptConnector = (connector, engine, HB, execution_listener)->
|
||||
|
||||
for name, f of ConnectorClass
|
||||
connector[name] = f
|
||||
|
||||
send_ = (o)->
|
||||
if o.uid.creator is HB.getUserId() and (typeof o.uid.op_number isnt "string")
|
||||
connector.broadcast o
|
||||
@ -46,6 +50,7 @@ adaptConnector = (connector, engine, HB, execution_listener)->
|
||||
connector.getHB = getHB
|
||||
connector.applyHB = applyHB
|
||||
|
||||
connector.receive_handlers = []
|
||||
connector.receive_handlers.push (sender, op)->
|
||||
if op.uid.creator isnt HB.getUserId()
|
||||
engine.applyOp op
|
||||
|
279
lib/ConnectorClass.coffee
Normal file
279
lib/ConnectorClass.coffee
Normal file
@ -0,0 +1,279 @@
|
||||
|
||||
module.exports =
|
||||
#
|
||||
# @params new Connector(syncMode, role)
|
||||
# @param syncMode {String} is either "syncAll" or "master-slave".
|
||||
# @param role {String} The role of this client
|
||||
# (slave or master (only used when syncMode is master-slave))
|
||||
#
|
||||
init: (options)->
|
||||
req = (name, choices)=>
|
||||
if options[name]?
|
||||
if (not choices?) or choices.some((c)->c is options[name])
|
||||
@[name] = options[name]
|
||||
else
|
||||
throw new Error "You can set the '"+name+"' option to one of the following choices: "+JSON.encode(choices)
|
||||
else
|
||||
throw new Error "You must specify "+name+", when initializing the Connector!"
|
||||
|
||||
req "syncMode", ["syncAll", "master-slave"]
|
||||
req "role", ["master", "slave"]
|
||||
req "user_id"
|
||||
@on_user_id_set(@user_id)
|
||||
|
||||
# A Master should sync with everyone! TODO: really? - for now its safer this way!
|
||||
if @role is "master"
|
||||
@syncMode = "syncAll"
|
||||
|
||||
# is set to true when this is synced with all other connections
|
||||
@is_synced = false
|
||||
# true, iff the client is currently syncing
|
||||
@is_syncing = false
|
||||
# Peerjs Connections: key: conn-id, value: object
|
||||
@connections = {}
|
||||
# List of functions that shall process incoming data
|
||||
# @receive_handlers = [] # this is already set in the ConnectorAdapter!
|
||||
|
||||
# whether this instance is bound to any y instance
|
||||
@is_bound_to_y = false
|
||||
@connections = {}
|
||||
@current_sync_target = null
|
||||
|
||||
isRoleMaster: ->
|
||||
@role is "master"
|
||||
|
||||
isRoleSlave: ->
|
||||
@role is "slave"
|
||||
|
||||
findNewSyncTarget: ()->
|
||||
@current_sync_target = null
|
||||
if @syncMode is "syncAll"
|
||||
for user, c of @connections
|
||||
if not c.is_synced
|
||||
@performSync user
|
||||
break
|
||||
null
|
||||
|
||||
userLeft: (user)->
|
||||
delete @connections[user]
|
||||
@findNewSyncTarget()
|
||||
|
||||
userJoined: (user, role)->
|
||||
if not role?
|
||||
throw new Error "Internal: You must specify the role of the joined user!"
|
||||
# a user joined the room
|
||||
@connections[user] =
|
||||
is_synced : false
|
||||
|
||||
if (not @is_synced) or @syncMode is "syncAll"
|
||||
if @syncMode is "syncAll"
|
||||
@performSync user
|
||||
else if role is "master"
|
||||
# TODO: What if there are two masters? Prevent sending everything two times!
|
||||
@performSyncWithMaster user
|
||||
|
||||
|
||||
#
|
||||
# Execute a function _when_ we are connected. If not connected, wait until connected.
|
||||
# @param f {Function} Will be executed on the PeerJs-Connector context.
|
||||
#
|
||||
whenSynced: (args)->
|
||||
if args.constructore is Function
|
||||
args = [args]
|
||||
if @is_synced
|
||||
args[0].apply this, args[1..]
|
||||
else
|
||||
@compute_when_synced ?= []
|
||||
@compute_when_synced.push args
|
||||
|
||||
#
|
||||
# Execute an function when a message is received.
|
||||
# @param f {Function} Will be executed on the PeerJs-Connector context. f will be called with (sender_id, broadcast {true|false}, message).
|
||||
#
|
||||
onReceive: (f)->
|
||||
@receive_handlers.push f
|
||||
|
||||
###
|
||||
# Broadcast a message to all connected peers.
|
||||
# @param message {Object} The message to broadcast.
|
||||
#
|
||||
broadcast: (message)->
|
||||
throw new Error "You must implement broadcast!"
|
||||
|
||||
#
|
||||
# Send a message to a peer, or set of peers
|
||||
#
|
||||
send: (peer_s, message)->
|
||||
throw new Error "You must implement send!"
|
||||
###
|
||||
|
||||
#
|
||||
# perform a sync with a specific user.
|
||||
#
|
||||
performSync: (user)->
|
||||
if not @current_sync_target?
|
||||
@current_sync_target = user
|
||||
@send user,
|
||||
sync_step: "getHB"
|
||||
data: @getStateVector()
|
||||
|
||||
#
|
||||
# When a master node joined the room, perform this sync with him. It will ask the master for the HB,
|
||||
# and will broadcast his own HB
|
||||
#
|
||||
performSyncWithMaster: (user)->
|
||||
if not @is_syncing
|
||||
@current_sync_target = user
|
||||
@is_syncing = true
|
||||
@send user,
|
||||
sync_step: "getHB"
|
||||
send_again: "true"
|
||||
data: []
|
||||
hb = @getHB([]).hb
|
||||
_hb = []
|
||||
for o in hb
|
||||
_hb.push o
|
||||
if _hb.length > 30
|
||||
@broadcast
|
||||
sync_step: "applyHB_"
|
||||
data: _hb
|
||||
_hb = []
|
||||
@broadcast
|
||||
sync_step: "applyHB"
|
||||
data: _hb
|
||||
#
|
||||
# You are sure that all clients are synced, call this function.
|
||||
#
|
||||
setStateSynced: ()->
|
||||
if not @is_synced
|
||||
@is_synced = true
|
||||
for f in @compute_when_synced
|
||||
f()
|
||||
delete @compute_when_synced
|
||||
null
|
||||
|
||||
#
|
||||
# You received a raw message, and you know that it is intended for to Yjs. Then call this function.
|
||||
#
|
||||
receiveMessage: (sender, res)->
|
||||
if not res.sync_step?
|
||||
for f in @receive_handlers
|
||||
f sender, res
|
||||
else
|
||||
if res.sync_step is "getHB"
|
||||
data = @getHB(res.data)
|
||||
hb = data.hb
|
||||
_hb = []
|
||||
if @is_synced
|
||||
sendApplyHB = ()->
|
||||
|
||||
for o in hb
|
||||
_hb.push o
|
||||
if _hb.length > 30
|
||||
@send sender,
|
||||
sync_step: "applyHB_"
|
||||
data: _hb
|
||||
_hb = []
|
||||
if @is_synced
|
||||
@send sender,
|
||||
sync_s tep: "applyHB"
|
||||
data: _hb
|
||||
|
||||
if res.send_again?
|
||||
send_again = do (sv = data.state_vector)=>
|
||||
()=>
|
||||
hb = @getHB(sv).hb
|
||||
@send sender,
|
||||
sync_step: "applyHB",
|
||||
data: hb
|
||||
sent_again: "true"
|
||||
setTimeout send_again, 3000
|
||||
else if res.sync_step is "applyHB"
|
||||
@applyHB(res.data)
|
||||
|
||||
if (@syncMode is "syncAll" or res.sent_again?) and not @is_synced
|
||||
@setStateSynced()
|
||||
@connections[sender].is_synced = true
|
||||
@findNewSyncTarget()
|
||||
|
||||
else if res.sync_step is "applyHB_"
|
||||
@applyHB(res.data)
|
||||
|
||||
|
||||
# Currently, the HB encodes operations as JSON. For the moment I want to keep it
|
||||
# that way. Maybe we support encoding in the HB as XML in the future, but for now I don't want
|
||||
# too much overhead. Y is very likely to get changed a lot in the future
|
||||
#
|
||||
# Because we don't want to encode JSON as string (with character escaping, wich makes it pretty much unreadable)
|
||||
# we encode the JSON as XML.
|
||||
#
|
||||
# When the HB support encoding as XML, the format should look pretty much like this.
|
||||
|
||||
# does not support primitive values as array elements
|
||||
# expects an ltx (less than xml) object
|
||||
parseMessageFromXml: (m)->
|
||||
parse_array = (node)->
|
||||
for n in node.children
|
||||
if n.getAttribute("isArray") is "true"
|
||||
parse_array n
|
||||
else
|
||||
parse_object n
|
||||
|
||||
parse_object = (node)->
|
||||
json = {}
|
||||
for name, value of node.attrs
|
||||
int = parseInt(value)
|
||||
if isNaN(int) or (""+int) isnt value
|
||||
json[name] = value
|
||||
else
|
||||
json[name] = int
|
||||
for n in node.children
|
||||
name = n.name
|
||||
if n.getAttribute("isArray") is "true"
|
||||
json[name] = parse_array n
|
||||
else
|
||||
json[name] = parse_object n
|
||||
json
|
||||
parse_object m
|
||||
|
||||
# encode message in xml
|
||||
# we use string because Strophe only accepts an "xml-string"..
|
||||
# So {a:4,b:{c:5}} will look like
|
||||
# <y a="4">
|
||||
# <b c="5"></b>
|
||||
# </y>
|
||||
# m - ltx element
|
||||
# json - guess it ;)
|
||||
#
|
||||
encodeMessageToXml: (m, json)->
|
||||
# attributes is optional
|
||||
encode_object = (m, json)->
|
||||
for name,value of json
|
||||
if not value?
|
||||
# nop
|
||||
else if value.constructor is Object
|
||||
encode_object m.c(name), value
|
||||
else if value.constructor is Array
|
||||
encode_array m.c(name), value
|
||||
else
|
||||
m.setAttribute(name,value)
|
||||
m
|
||||
encode_array = (m, array)->
|
||||
m.setAttribute("isArray","true")
|
||||
for e in array
|
||||
if e.constructor is Object
|
||||
encode_object m.c("array-element"), e
|
||||
else
|
||||
encode_array m.c("array-element"), e
|
||||
m
|
||||
if json.constructor is Object
|
||||
encode_object m.c("y",{xmlns:"http://y.ninja/connector-stanza"}), json
|
||||
else if json.constructor is Array
|
||||
encode_array m.c("y",{xmlns:"http://y.ninja/connector-stanza"}), json
|
||||
else
|
||||
throw new Error "I can't encode this json!"
|
||||
|
||||
setIsBoundToY: ()->
|
||||
@on_bound_to_y?()
|
||||
delete @when_bound_to_y
|
||||
@is_bound_to_y = true
|
@ -31,9 +31,9 @@ class HistoryBuffer
|
||||
throw new Error "You are re-assigning an old user id - this is not (yet) possible!"
|
||||
@buffer[id] = own
|
||||
delete @buffer[@user_id]
|
||||
|
||||
@operation_counter[id] = @operation_counter[@user_id]
|
||||
delete @operation_counter[@user_id]
|
||||
if @operation_counter[@user_id]?
|
||||
@operation_counter[id] = @operation_counter[@user_id]
|
||||
delete @operation_counter[@user_id]
|
||||
@user_id = id
|
||||
|
||||
emptyGarbage: ()=>
|
||||
@ -196,7 +196,7 @@ class HistoryBuffer
|
||||
# you renew your own state_vector to the state_vector of the other user
|
||||
renewStateVector: (state_vector)->
|
||||
for user,state of state_vector
|
||||
if (not @operation_counter[user]?) or (@operation_counter[user] < state_vector[user])
|
||||
if ((not @operation_counter[user]?) or (@operation_counter[user] < state_vector[user])) and state_vector[user]?
|
||||
@operation_counter[user] = state_vector[user]
|
||||
|
||||
#
|
||||
|
@ -184,7 +184,7 @@ module.exports = (HB)->
|
||||
#
|
||||
#
|
||||
callEventDecorator: (events)->
|
||||
if not @isDeleted()
|
||||
if not (@isDeleted() or @getLastOperation().isDeleted())
|
||||
for event in events
|
||||
for name,prop of @event_properties
|
||||
event[name] = prop
|
||||
|
@ -6,11 +6,11 @@ adaptConnector = require "./ConnectorAdapter"
|
||||
|
||||
createY = (connector)->
|
||||
user_id = null
|
||||
if connector.id?
|
||||
user_id = connector.id # TODO: change to getUniqueId()
|
||||
if connector.user_id?
|
||||
user_id = connector.user_id # TODO: change to getUniqueId()
|
||||
else
|
||||
user_id = "_temp"
|
||||
connector.onUserIdSet (id)->
|
||||
connector.on_user_id_set = (id)->
|
||||
user_id = id
|
||||
HB.resetUserId id
|
||||
HB = new HistoryBuffer user_id
|
||||
|
@ -1,8 +1,8 @@
|
||||
|
||||
<link rel="import" href="../polymer/polymer.html">
|
||||
|
||||
<polymer-element name="y-object" hidden attributes="val connector y">
|
||||
</polymer-element>
|
||||
<polymer-element name="y-property" hidden attributes="val name y">
|
||||
</polymer-element>
|
||||
|
||||
<script src="./build/browser/y-object.js"></script>
|
||||
<script src="./y-object.js"></script>
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user