This commit is contained in:
Steven Lucco 2017-02-22 14:38:51 -08:00
Родитель e80e62fe99
Коммит 1252be0222
3 изменённых файлов: 144 добавлений и 41 удалений

Просмотреть файл

@ -421,40 +421,6 @@ function itreeCheckedTest() {
}
class TestClient {
constructor(public id: number, public text:string, public testService: TestService) {
}
testInsert(pos: number, s: string) {
}
}
class TestService {
todoIndex = 0;
ops: CollabString.Op[] = [];
clients: TestClient[] = [];
msg(op: CollabString.Op) {
this.ops.push(op);
this.process();
}
process() {
let outOps: CollabString.Op[]=[];
for (let i=this.todoIndex, len = this.ops.length;i<len;i++) {
let op = this.ops[i];
if (i>this.todoIndex) {
}
else {
outOps.push(op);
}
}
}
}
//simpleTest();
//fileTest1();
//integerTest1();

Просмотреть файл

@ -4,9 +4,10 @@ export enum OpType {
}
export interface Op {
transform(op: Op, opsOut: Op[], outIndex?: number);
transform(op: Op, opsOut: Op[]);
opType: OpType;
clientId?: number;
seq?: number;
}
export class InsertTextOp implements Op {
@ -15,7 +16,7 @@ export class InsertTextOp implements Op {
}
transform(op: Op, opsOut: Op[], outIndex = 0) {
transform(op: Op, opsOut: Op[]) {
let len = this.text.length;
switch (op.opType) {
case OpType.Insert: {
@ -24,14 +25,15 @@ export class InsertTextOp implements Op {
if (this.pos <= pos2) {
pos2 += len;
}
opsOut[outIndex] = new InsertTextOp(pos2, insertOp.text);
opsOut.push(new InsertTextOp(pos2, insertOp.text));
break;
}
case OpType.RemoveRange: {
let removeOp = <RemoveRangeOp>op;
let start = removeOp.start;
let end = removeOp.end;
let start2: number, end2: number;
let start2 = -1;
let end2 = -1;
if (this.pos <= start) {
start += len;
end += len;
@ -40,9 +42,11 @@ export class InsertTextOp implements Op {
end2 = end + len;
end = this.pos;
start2 = this.pos + len;
opsOut[outIndex + 1] = new RemoveRangeOp(start2, end2);
}
opsOut[outIndex] = new RemoveRangeOp(start, end);
opsOut.push(new RemoveRangeOp(start, end));
if (start2 >= 0) {
opsOut.push(new RemoveRangeOp(start2, end2));
}
break;
}
}
@ -95,6 +99,140 @@ export class RemoveRangeOp implements Op {
}
}
}
}
const Nope = -1;
function editFlat(source: string, s: number, dl: number, nt = "") {
return source.substring(0, s) + nt + source.substring(s + dl, source.length);
}
export class TestClient {
snackOps = <Op[]>[];
snackStart = 0;
seq = Nope;
clientId = Nope;
constructor(public id: number, public text: string, public testService: TestService) {
testService.registerClient(this);
}
insert(pos: number, s: string) {
this.snackOps.push(new InsertTextOp(pos, s));
}
remove(start: number, end: number) {
this.snackOps.push(new RemoveRangeOp(start, end));
}
flush() {
}
apply(op: Op) {
switch (op.opType) {
case OpType.Insert: {
let insertOp = <InsertTextOp>op;
editFlat(this.text, insertOp.pos, 0, insertOp.text);
break;
}
case OpType.RemoveRange: {
let removeOp = <RemoveRangeOp>op;
editFlat(this.text, removeOp.start, removeOp.end - removeOp.start);
break;
}
}
}
setClientId(id: number) {
this.clientId = id;
}
msg(serverOps: Op[]) {
let xformServerOps = <Op[]>[];
// transform each server op by any ops sent but not acknowledged
for (let serverOp of serverOps) {
if (serverOp.clientId != this.clientId) {
for (let snackIndex = this.snackStart, snackLen = this.snackOps.length; snackIndex < snackLen; snackIndex++) {
let snackOp = this.snackOps[snackIndex];
snackOp.transform(serverOp, xformServerOps);
}
}
else {
// assumes snacks acknowledged in order
this.snackStart++;
}
this.seq = serverOp.seq;
}
// apply transformed server ops
for (let xformOp of xformServerOps) {
this.apply(xformOp);
}
// copy down remaining sent not acknowledged ops
if (this.snackStart > 0) {
let remainingCount = this.snackOps.length - this.snackStart;
for (let i = 0; i < remainingCount; i++) {
this.snackOps[i] = this.snackOps[i + this.snackStart];
}
this.snackStart = 0;
this.snackOps.length = remainingCount;
}
}
}
export class TestService {
revisionHistory = <Op[]>[];
clients = <TestClient[]>[];
constructor(public text: string) {
}
registerClient(cli: TestClient) {
cli.setClientId(this.clients.length);
this.clients.push(cli);
}
apply(op: Op) {
switch (op.opType) {
case OpType.Insert: {
let insertOp = <InsertTextOp>op;
editFlat(this.text, insertOp.pos, 0, insertOp.text);
break;
}
case OpType.RemoveRange: {
let removeOp = <RemoveRangeOp>op;
editFlat(this.text, removeOp.start, removeOp.end - removeOp.start);
break;
}
}
}
check() {
for (let client of this.clients) {
if (client.text != this.text) {
console.log(`mismatch with client ${client.clientId}`);
}
}
}
msg(ops: Op[]) {
for (let op of ops) {
let clientSeq = op.seq;
let clientOps = <Op[]>[];
let xformOps = [op];
// apply to client op any ops client has not seen
for (let i = clientSeq + 1, len = this.revisionHistory.length; i < len; i++) {
let temp = clientOps;
clientOps = xformOps;
xformOps = temp;
let priorOp = this.revisionHistory[i];
for (let clientOp of clientOps) {
priorOp.transform(clientOp, xformOps);
}
}
for (let xformOp of xformOps) {
this.apply(xformOp);
this.revisionHistory.push(xformOp);
}
for (let client of this.clients) {
client.msg(xformOps);
}
this.check();
}
}
}

Просмотреть файл

@ -10,7 +10,6 @@ export interface MapLike<T> {
export class PropertySet {
propertyMap:MapLike<Property> = createDictionaryObject<Property>();
setProperty(name: )
}
export interface PropertyDictionary {