/* @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 { 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) { 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); } } };