246 lines
6.6 KiB
JavaScript
246 lines
6.6 KiB
JavaScript
/* @flow */
|
|
|
|
// Op is anything that we could get from the OperationStore.
|
|
type Op = Object;
|
|
type Id = [string, number];
|
|
|
|
type List = {
|
|
id: Id,
|
|
start: Insert,
|
|
end: Insert
|
|
};
|
|
|
|
type Insert = {
|
|
id: Id,
|
|
left: Insert,
|
|
right: Insert,
|
|
origin: Insert,
|
|
parent: List,
|
|
content: any
|
|
};
|
|
|
|
var Struct = {
|
|
Operation: { //eslint-disable-line no-unused-vars
|
|
create: function*(op : Op) : Struct.Operation {
|
|
var user = this.store.y.connector.userId;
|
|
var state = yield* this.getState(user);
|
|
op.id = [user, state.clock];
|
|
return yield* this.addOperation(op);
|
|
}
|
|
},
|
|
Insert: {
|
|
/*{
|
|
content: any,
|
|
left: Id,
|
|
right: Id,
|
|
parent: Id,
|
|
parentSub: string (optional)
|
|
}
|
|
*/
|
|
create: function*( op: Op ) : Insert {
|
|
if ( op.left === undefined
|
|
|| op.right === undefined
|
|
|| op.parent === undefined ) {
|
|
throw new Error("You must define left, right, and parent!");
|
|
}
|
|
op.origin = op.left;
|
|
op.struct = "Insert";
|
|
yield* Struct.Operation.create.call(this, op);
|
|
|
|
if (op.left != null) {
|
|
op.left.right = op.id;
|
|
yield* this.setOperation(op.left);
|
|
}
|
|
if (op.right != null) {
|
|
op.right.left = op.id;
|
|
yield* this.setOperation(op.right);
|
|
}
|
|
return op;
|
|
},
|
|
requiredOps: function(op, ids){
|
|
if(op.left != null){
|
|
ids.push(op.left);
|
|
}
|
|
if(op.right != null){
|
|
ids.push(op.right);
|
|
}
|
|
return ids;
|
|
},
|
|
getDistanceToOrigin: function *(op){
|
|
var d = 0;
|
|
var o = yield this.getOperation(op.left);
|
|
while (op.origin !== (o ? o.id : null)) {
|
|
d++;
|
|
o = yield this.getOperation(o.left);
|
|
}
|
|
return d;
|
|
},
|
|
/*
|
|
# $this has to find a unique position between origin and the next known character
|
|
# case 1: $origin equals $o.origin: the $creator parameter decides if left or right
|
|
# let $OL= [o1,o2,o3,o4], whereby $this is to be inserted between o1 and o4
|
|
# o2,o3 and o4 origin is 1 (the position of o2)
|
|
# there is the case that $this.creator < o2.creator, but o3.creator < $this.creator
|
|
# then o2 knows o3. Since on another client $OL could be [o1,o3,o4] the problem is complex
|
|
# therefore $this would be always to the right of o3
|
|
# case 2: $origin < $o.origin
|
|
# if current $this insert_position > $o origin: $this ins
|
|
# else $insert_position will not change
|
|
# (maybe we encounter case 1 later, then this will be to the right of $o)
|
|
# case 3: $origin > $o.origin
|
|
# $this insert_position is to the left of $o (forever!)
|
|
*/
|
|
execute: function*(op){
|
|
var distanceToOrigin = yield* Struct.Insert.getDistanceToOrigin(op); // most cases: 0 (starts from 0)
|
|
var i = distanceToOrigin; // loop counter
|
|
var o = yield* this.getOperation(this.left);
|
|
o = yield* this.getOperation(o.right);
|
|
var tmp;
|
|
while (true) {
|
|
if (o.id !== this.right){
|
|
if (Struct.Insert.getDistanceToOrigin(o) === i) {
|
|
// case 1
|
|
if (o.id[0] < op.id[0]) {
|
|
op.left = o;
|
|
distanceToOrigin = i + 1;
|
|
}
|
|
} else if ((tmp = Struct.Insert.getDistanceToOrigin(o)) < i) {
|
|
// case 2
|
|
if (i - distanceToOrigin <= tmp) {
|
|
op.left = o;
|
|
distanceToOrigin = i + 1;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
i++;
|
|
o = yield* this.getOperation(o.next_cl);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// reconnect..
|
|
var left = null;
|
|
var right = null;
|
|
if (op.left != null) {
|
|
left = this.getOperation(op.left);
|
|
left.right = op.id;
|
|
yield* this.setOperation(left);
|
|
}
|
|
if (op.right != null) {
|
|
right = this.getOperation(op.right);
|
|
right.left = op.id;
|
|
yield* this.setOperation(right);
|
|
}
|
|
op.left = left;
|
|
op.right = right;
|
|
yield* this.setOperation(op);
|
|
|
|
// notify parent
|
|
var parent = this.getOperation(op.parent);
|
|
if (op.parentSub != null) {
|
|
if (right == null) {
|
|
parent.map[op.parentSub] = op.id;
|
|
yield* this.setOperation(parent);
|
|
}
|
|
} else {
|
|
if (right == null || left == null) {
|
|
if (right == null) {
|
|
parent.end = op.id;
|
|
}
|
|
if (left == null) {
|
|
parent.start = op.id;
|
|
}
|
|
yield* this.setOperation(parent);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
List: {
|
|
create: function*( op : Op){
|
|
op.start = null;
|
|
op.end = null;
|
|
op.struct = "List";
|
|
return yield* Struct.Operation.create.call(this, op);
|
|
},
|
|
requiredOps: function(op, ids){
|
|
if (op.start != null) {
|
|
ids.push(op.start);
|
|
}
|
|
if (op.end != null){
|
|
ids.push(op.end);
|
|
}
|
|
return ids;
|
|
},
|
|
execute: function* () {
|
|
// nop
|
|
},
|
|
ref: function* (op : Op, pos : number) : Insert {
|
|
var o = op.start;
|
|
while ( pos !== 0 || o != null) {
|
|
o = (yield* this.getOperation(o)).right;
|
|
pos--;
|
|
}
|
|
return (o == null) ? null : yield* this.getOperation(o);
|
|
},
|
|
map: function* (o : Op, f : Function) : Array<any> {
|
|
o = o.start;
|
|
var res = [];
|
|
while ( o != null) {
|
|
var operation = yield* this.getOperation(o);
|
|
res.push(f(operation.content));
|
|
o = operation.right;
|
|
}
|
|
return res;
|
|
},
|
|
insert: function* (op, pos : number, contents : Array<any>) {
|
|
var o = yield* Struct.List.ref.call(this, op, pos);
|
|
var or = yield* this.getOperation(o.right);
|
|
for (var content of contents) {
|
|
var insert = {
|
|
left: o,
|
|
right: or,
|
|
content: content,
|
|
parent: op
|
|
};
|
|
o = yield* Struct.Insert.create.call(this, insert);
|
|
}
|
|
}
|
|
},
|
|
Map: {
|
|
create: function*( op : Op ){
|
|
op.map = {};
|
|
op.struct = "Map";
|
|
return yield* Struct.Operation.create.call(this, op);
|
|
},
|
|
requiredOps: function(op, ids){
|
|
for (var end of op.map) {
|
|
ids.push(end);
|
|
}
|
|
return ids;
|
|
},
|
|
execute: function* () {
|
|
// nop
|
|
},
|
|
get: function* (op, name) {
|
|
return yield* this.getOperation(op.map[name].end);
|
|
},
|
|
set: function* (op, name, value) {
|
|
var end = op.map[name];
|
|
if (end == null) {
|
|
end = null;
|
|
op.map[name] = end;
|
|
}
|
|
var insert = {
|
|
left: end,
|
|
right: null,
|
|
content: value,
|
|
parent: op.id,
|
|
parentSub: name
|
|
};
|
|
yield* Struct.Insert.create.call(this, insert);
|
|
}
|
|
}
|
|
};
|