Skip to content

Commit 9f7bd16

Browse files
Add Merged OT System for processing multiple independent operations
1 parent c01d33b commit 9f7bd16

13 files changed

Lines changed: 1129 additions & 863 deletions

package-lock.json

Lines changed: 548 additions & 847 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
},
3939
"dependencies": {
4040
"cross-fetch": "^3.0.1",
41-
"lodash": "^4.17.4",
41+
"lodash": "^4.17.15",
4242
"node-fetch": "^2.3.0",
4343
"tinyqueue": "^1.2.2",
4444
"whatwg-fetch": "^3.0.0"

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
export {OTStateManager} from './ot/OTStateManager';
1010
export {TransformResult} from './ot/OTSystem/TransformResult';
1111
export {OTSystemBuilder} from './ot/OTSystem/OTSystemBuilder';
12+
export {MergedOTSystem} from './ot/OTSystem/MergedOTSystem';
1213
export {OTCommit} from './ot/OTCommit';
1314
export {ClientOTNode} from './ot/ClientOTNode';
1415
export {commitsToGraphviz, makeCheckpointForCommit, merge} from './ot/otUtils';

src/ot/OTStateManager.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import assert from 'assert';
1212
import EventEmitter from 'events';
1313
import {serial} from '../common/utils';
1414

15-
import type OTSystem from "./OTSystem/OTSystem";
15+
import type OTSystemImpl from "./OTSystem/OTSystemImpl";
1616
import type {OTOperation} from "./interfaces/OTOperation";
1717
import type {OTNode, fetchData} from "./interfaces/OTNode";
1818

@@ -23,7 +23,7 @@ const DEFAULT_RETRY_TIMEOUT = 1000;
2323
export class OTStateManager<TKey, TState> {
2424
_initState: provider<TState>;
2525
_otNode: OTNode<TKey, TState>;
26-
_otSystem: OTSystem<TState>;
26+
_otSystem: OTSystemImpl<TState>;
2727
_retryTimeout: number;
2828

2929
_state: TState;
@@ -38,7 +38,7 @@ export class OTStateManager<TKey, TState> {
3838
constructor(
3939
initState: provider<TState>,
4040
node: OTNode<TKey, TState>,
41-
otSystem: OTSystem<TState>,
41+
otSystem: OTSystemImpl<TState>,
4242
retryTimeout: number = DEFAULT_RETRY_TIMEOUT
4343
) {
4444
this._initState = initState;

src/ot/OTSystem/MergedOTSystem.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Copyright (с) 2019-present, SoftIndex LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
// @flow
10+
11+
import {TransformResult} from './TransformResult';
12+
import {OTOperation} from "../interfaces/OTOperation";
13+
import type {OTSystem} from "../interfaces/OTSystem";
14+
15+
export type constructor2<S> = (Array<$FlowFixMe>, Array<$FlowFixMe>) => OTOperation<S>;
16+
export type constructor3<S> = (Array<$FlowFixMe>, Array<$FlowFixMe>, Array<$FlowFixMe>) => OTOperation<S>;
17+
export type constructor4<S> = (Array<$FlowFixMe>, Array<$FlowFixMe>, Array<$FlowFixMe>, Array<$FlowFixMe>) => OTOperation<S>;
18+
export type getter<S, O> = $FlowFixMe => Array<OTOperation<O>>;
19+
20+
export class MergedOTSystem<S, S1, S2> implements OTSystem<S> {
21+
_constructor: constructor2<S>;
22+
_getter1: getter<S, S1>;
23+
_otSystem1: OTSystem<S1>;
24+
_getter2: getter<S, S2>;
25+
_otSystem2: OTSystem<S2>;
26+
27+
constructor(constructor: constructor2<S>,
28+
getter1: getter<S, S1>,
29+
otSystem1: OTSystem<S1>,
30+
getter2: getter<S, S2>,
31+
otSystem2: OTSystem<S2>
32+
) {
33+
this._constructor = constructor;
34+
this._getter1 = getter1;
35+
this._otSystem1 = otSystem1;
36+
this._getter2 = getter2;
37+
this._otSystem2 = otSystem2;
38+
}
39+
40+
static merge2<S, S1, S2>(constructor: constructor2<S>,
41+
getter1: getter<S, S1>,
42+
otSystem1: OTSystem<S1>,
43+
getter2: getter<S, S2>,
44+
otSystem2: OTSystem<S2>): OTSystem<S> {
45+
return new MergedOTSystem(constructor, getter1, otSystem1, getter2, otSystem2);
46+
}
47+
48+
static merge3<S, S1, S2, S3>(constructor: constructor3<S>,
49+
getter1: getter<S, S1>,
50+
otSystem1: OTSystem<S1>,
51+
getter2: getter<S, S2>,
52+
otSystem2: OTSystem<S2>,
53+
getter3: getter<S, S3>,
54+
otSystem3: OTSystem<S3>): OTSystem<S> {
55+
56+
const premerged = this.merge2(Helper.create,
57+
helper => helper.ops1, otSystem1,
58+
helper => helper.ops2, otSystem2);
59+
60+
61+
return this.merge2((helpers, ops) => {
62+
let helper = extractHelper(helpers);
63+
return constructor(helper.ops1, helper.ops2, ops);
64+
},
65+
multiOP => combine(getter1(multiOP), getter2(multiOP), Helper.create), premerged,
66+
getter3, otSystem3);
67+
}
68+
69+
static merge4<S, S1, S2, S3, S4>(constructor: constructor4<S>,
70+
getter1: getter<S, S1>,
71+
otSystem1: OTSystem<S1>,
72+
getter2: getter<S, S2>,
73+
otSystem2: OTSystem<S2>,
74+
getter3: getter<S, S3>,
75+
otSystem3: OTSystem<S3>,
76+
getter4: getter<S, S4>,
77+
otSystem4: OTSystem<S4>): OTSystem<S> {
78+
79+
const premerged1 = this.merge2(Helper.create,
80+
helper => helper.ops1, otSystem1,
81+
helper => helper.ops2, otSystem2);
82+
83+
const premerged2 = this.merge2(Helper.create,
84+
helper => helper.ops1, otSystem3,
85+
helper => helper.ops2, otSystem4);
86+
87+
return this.merge2((helpers1, helpers2) => {
88+
let helper1 = extractHelper(helpers1);
89+
let helper2 = extractHelper(helpers2);
90+
return constructor(helper1.ops1, helper1.ops2, helper2.ops1, helper2.ops2)
91+
},
92+
multiOp => combine(getter1(multiOp), getter2(multiOp), Helper.create), premerged1,
93+
multiOp => combine(getter3(multiOp), getter4(multiOp), Helper.create), premerged2);
94+
}
95+
96+
/**
97+
* Is operation empty
98+
*/
99+
isEmpty(op: OTOperation<S>): boolean {
100+
for (let operation of this._getter1(op)) {
101+
if (!this._otSystem1.isEmpty(operation)) {
102+
return false;
103+
}
104+
}
105+
for (let operation of this._getter2(op)) {
106+
if (!this._otSystem2.isEmpty(operation)) {
107+
return false;
108+
}
109+
}
110+
return true;
111+
}
112+
113+
/**
114+
* Transform two diffs
115+
*/
116+
transform(leftOps: Array<OTOperation<S>>, rightOps: Array<OTOperation<S>>): TransformResult<S> {
117+
let leftOps1 = this._collect(leftOps, this._getter1);
118+
let leftOps2 = this._collect(leftOps, this._getter2);
119+
let rightOps1 = this._collect(rightOps, this._getter1);
120+
let rightOps2 = this._collect(rightOps, this._getter2);
121+
122+
let transform1 = this._otSystem1.transform(leftOps1, rightOps1);
123+
let transform2 = this._otSystem2.transform(leftOps2, rightOps2);
124+
125+
let left = combine(transform1.leftOps, transform2.leftOps, this._constructor);
126+
let right = combine(transform1.rightOps, transform2.rightOps, this._constructor);
127+
128+
return TransformResult.of(left, right);
129+
}
130+
131+
/**
132+
* Squash operations
133+
*/
134+
squash(ops: Array<OTOperation<S>>): Array<OTOperation<S>> {
135+
if (ops.length === 0) return ops;
136+
let squashed1 = this._otSystem1.squash(this._collect(ops, this._getter1));
137+
let squashed2 = this._otSystem2.squash(this._collect(ops, this._getter2));
138+
return combine(squashed1, squashed2, this._constructor);
139+
}
140+
141+
/**
142+
* Invert operations
143+
*/
144+
invert(ops: Array<OTOperation<S>>): Array<OTOperation<S>> {
145+
if (ops.length === 0) return ops;
146+
let inverted1 = this._otSystem1.invert(this._collect(ops, this._getter1));
147+
let inverted2 = this._otSystem2.invert(this._collect(ops, this._getter2));
148+
return combine(inverted1, inverted2, this._constructor);
149+
}
150+
151+
_collect<OP>(ops: Array<OTOperation<S>>, getter: OTOperation<S> => Array<OTOperation<OP>>): Array<OTOperation<OP>> {
152+
let result: Array<OTOperation<OP>> = [];
153+
ops.map(el => getter(el)).forEach(ops => result.push(...ops));
154+
return result;
155+
}
156+
}
157+
158+
// region helpers
159+
function combine<S, S1, S2>(ops1: Array<S1>, ops2: Array<S2>, constructor: (Array<S1>, Array<S2>) => S): Array<S> {
160+
if (ops1.length === 0 && ops2.length === 0) {
161+
return [];
162+
} else {
163+
return [constructor(ops1, ops2)];
164+
}
165+
}
166+
167+
function extractHelper<S, S1, S2>(helpers: Array<Helper<S, S1, S2>>): Helper<S, S1, S2> {
168+
if (helpers.length > 1) {
169+
throw new Error('Invalid state, there can only be one or zero helpers');
170+
}
171+
return helpers.length === 0 ? new Helper([], []) : helpers[0];
172+
}
173+
174+
class Helper<S, S1, S2> implements OTOperation<S> {
175+
ops1: Array<OTOperation<S1>>;
176+
ops2: Array<OTOperation<S2>>;
177+
178+
constructor(ops1: Array<OTOperation<S1>>, ops2: Array<OTOperation<S2>>) {
179+
this.ops1 = ops1;
180+
this.ops2 = ops2;
181+
}
182+
183+
static create(ops1: Array<OTOperation<S1>>, ops2: Array<OTOperation<S2>>) {
184+
return new Helper(ops1, ops2);
185+
}
186+
187+
apply(state: S): S {
188+
throw new Error('Helper class');
189+
}
190+
}
191+
192+
// endregion

src/ot/OTSystem/OTSystemBuilder.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
// @flow
1010

11-
import OTSystem from "./OTSystem";
11+
import OTSystemImpl from "./OTSystemImpl";
1212
import {ensureMapValue} from '../../common/utils';
1313
import {OTOperation} from "../interfaces/OTOperation";
1414

15-
import type {transformer, squasher, emptyPredicate, inverter} from './OTSystem';
15+
import type {transformer, squasher, emptyPredicate, inverter} from './OTSystemImpl';
1616
import {TransformResult} from "./TransformResult";
1717

1818
export class OTSystemBuilder<S> {
@@ -28,8 +28,8 @@ export class OTSystemBuilder<S> {
2828
this._inverters = new Map();
2929
}
3030

31-
build(): OTSystem<S> {
32-
return new OTSystem(this._transformers, this._squashers, this._emptyPredicated, this._inverters);
31+
build(): OTSystemImpl<S> {
32+
return new OTSystemImpl(this._transformers, this._squashers, this._emptyPredicated, this._inverters);
3333
}
3434

3535
withTransformFunction(LeftOp: Class<OTOperation<S>>, RightOp: Class<OTOperation<S>>, transformer: transformer<S>): OTSystemBuilder<S> {
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010

1111
import {TransformResult} from './TransformResult';
1212
import {OTOperation} from "../interfaces/OTOperation";
13+
import type {OTSystem} from "../interfaces/OTSystem";
1314

1415
export type transformer<S> = ($FlowFixMe, $FlowFixMe) => TransformResult<S>;
1516
export type squasher<S> = ($FlowFixMe, $FlowFixMe) => ?OTOperation<S>;
1617
export type emptyPredicate<S> = ($FlowFixMe) => boolean;
1718
export type inverter<S> = ($FlowFixMe) => OTOperation<S>;
1819

19-
class OTSystem<S> {
20+
class OTSystemImpl<S> implements OTSystem<S> {
2021
_transformers: Map<Class<OTOperation<S>>, Map<Class<OTOperation<S>>, transformer<S>>>;
2122
_squashers: Map<Class<OTOperation<S>>, Map<Class<OTOperation<S>>, squasher<S>>>;
2223
_emptyPredicates: Map<Class<OTOperation<S>>, emptyPredicate<S>>;
@@ -219,4 +220,4 @@ class OTSystem<S> {
219220
}
220221
}
221222

222-
export default OTSystem;
223+
export default OTSystemImpl;

0 commit comments

Comments
 (0)