Skip to content

Commit 47ec682

Browse files
authored
Merge pull request #44 from hyperhyperspace/checkpoints
WIP checkpoint export/import for mutable types
2 parents 2169a30 + 9368de8 commit 47ec682

File tree

9 files changed

+560
-55
lines changed

9 files changed

+560
-55
lines changed

src/data/collections/mutable/MutableArray.ts

+21
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,27 @@ class MutableArray<T> extends BaseCollection<T> implements Collection<T> {
215215
this._hashes = [];
216216
this._ordinals = [];
217217
}
218+
219+
exportMutableState() {
220+
return {
221+
_elementsPerOrdinal: this._elementsPerOrdinal.toLiteral(),
222+
_ordinalsPerElement: this._ordinalsPerElement.toLiteral(),
223+
_elements: Object.fromEntries(this._elements.entries()),
224+
_currentInsertOpRefs: this._currentInsertOpRefs.toLiteral(),
225+
_currentInsertOpOrds: Object.fromEntries(this._currentInsertOpOrds),
226+
}
227+
}
228+
229+
importMutableState(state: any) {
230+
this._elementsPerOrdinal = ArrayMap.fromLiteral<Ordinal, Hash>(state._elementsPerOrdinal);
231+
this._ordinalsPerElement = ArrayMap.fromLiteral<Hash, Ordinal>(state._ordinalsPerElement);
232+
this._elements = new Map(Object.entries(state._elements));
233+
this._currentInsertOpRefs = DedupMultiMap.fromLiteral<Hash, HashReference<InsertOp<T>>>(state._currentInsertOpRefs);
234+
this._currentInsertOpOrds = new Map(Object.entries(state._currentInsertOpOrds));
235+
236+
this._needToRebuild = true;
237+
this.rebuild();
238+
}
218239

219240
async insertAt(element: T, idx: number, author?: Identity) {
220241
await this.insertManyAt([element], idx, author);

src/data/collections/mutable/MutableReference.ts

+21-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MutationOp } from '../../model/mutable/MutationOp';
33
import { HashedObject } from '../../model/immutable/HashedObject';
44
import { Timestamps } from 'util/timestamps';
55
import { Hash } from '../../model/hashing';
6-
import { ClassRegistry } from '../../model';
6+
import { ClassRegistry, Context, LiteralContext } from '../../model';
77
import { MultiMap } from 'util/multimap';
88
import { Identity } from 'data/identity';
99
import { BaseCollection, CollectionConfig, CollectionOp } from './Collection';
@@ -103,17 +103,35 @@ class MutableReference<T> extends BaseCollection<T> {
103103
}
104104

105105
exportMutableState() {
106+
107+
let objectValue : (LiteralContext|undefined);
108+
let literalValue : any;
109+
110+
if (this._value instanceof HashedObject) {
111+
const context = new Context();
112+
this._value.toContext(context);
113+
objectValue = context.toLiteralContext();
114+
} else {
115+
literalValue = this._value;
116+
}
117+
106118
return {
107119
_sequence: this._sequence,
108120
_timestamp: this._timestamp,
109-
_value: this._value
121+
_objectValue: objectValue,
122+
_literalValue: literalValue
110123
};
111124
}
112125

113126
importMutableState(state: any) {
114127
this._sequence = state._sequence;
115128
this._timestamp = state._timestamp;
116-
this._value = state._value;
129+
130+
if (state._objectValue !== undefined) {
131+
this._value = HashedObject.fromLiteralContext(state._objectValue) as any as T;
132+
} else {
133+
this._value = state._literalValue;
134+
}
117135
}
118136

119137
async validate(references: Map<Hash, HashedObject>) {
@@ -147,7 +165,6 @@ class RefUpdateOp<T> extends CollectionOp<T> {
147165
timestamp?: string;
148166
value?: T;
149167

150-
151168
constructor(targetObject?: MutableReference<T>, value?: T, sequence?: number, author?: Identity) {
152169
super(targetObject);
153170

src/data/collections/mutable/MutableSet.ts

+37-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { HashedSet } from 'data/model/immutable/HashedSet';
66
import { HashReference } from 'data/model/immutable/HashReference';
77
import { Logger, LogLevel } from 'util/logging';
88
import { MultiMap } from 'util/multimap';
9-
import { ClassRegistry } from 'data/model';
9+
import { ClassRegistry, Context, LiteralContext } from 'data/model';
1010
import { BaseCollection, Collection, CollectionConfig, CollectionOp } from './Collection';
1111
import { Identity } from 'data/identity';
1212

@@ -208,15 +208,46 @@ class MutableSet<T> extends BaseCollection<T> implements Collection<T> {
208208
}
209209

210210
exportMutableState() {
211+
212+
const literalElements = {} as any;
213+
const context = new Context();
214+
215+
for (const [hash, elmt] of this._elements.entries()) {
216+
if (elmt instanceof HashedObject) {
217+
elmt.toContext(context);
218+
} else {
219+
literalElements[hash] = elmt;
220+
}
221+
}
222+
211223
return {
212-
_elements: this._elements,
213-
_currentAddOpRefs: this._currentAddOpRefs
224+
_objectElements: context.toLiteralContext(),
225+
_literalElements: literalElements,
226+
_currentAddOpRefs: Object.fromEntries([...this._currentAddOpRefs.entries()].map(([key, value]) => [key, value.literalize().value]))
214227
};
215228
}
216229

217-
importMutableState(state: any): void {
218-
this._elements = new Map(Object.entries(state._elements));
219-
this._currentAddOpRefs = new Map(Object.entries(state._currentAddOpRefs));
230+
importMutableState(state: {
231+
_objectElements: LiteralContext,
232+
_literalElements: {[key: string]: any},
233+
_currentAddOpRefs: {[key: string]: any}
234+
}): void {
235+
236+
this._elements = new Map();
237+
238+
const context = new Context();
239+
context.fromLiteralContext(state._objectElements);
240+
241+
for (const hash of context.rootHashes) {
242+
this._elements.set(hash, HashedObject.fromContext(context, hash) as any as T);
243+
}
244+
245+
for (const [hash, literal] of Object.entries(state._literalElements)) {
246+
this._elements.set(hash, literal);
247+
}
248+
249+
const emptyContext = new Context();
250+
this._currentAddOpRefs = new Map(Object.entries(state._currentAddOpRefs).map(([key, value]) => [key, HashedSet.deliteralize(value, emptyContext)]));
220251
}
221252

222253
async validate(references: Map<Hash, HashedObject>) {

src/data/model/immutable/HashedSet.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import { Context } from '../literals/Context';
44

55
import { Dependency } from '../literals/LiteralUtils';
66

7+
type HashedSetLiteral = {
8+
value: { _type: string, _hashes: string[], _elements: any[] },
9+
dependencies: Map<Hash, Dependency>,
10+
}
11+
712
class HashedSet<T> {
813

914
hashedElements : Map<Hash, T>;
@@ -84,7 +89,7 @@ class HashedSet<T> {
8489
return result;
8590
}
8691

87-
literalize(path='', context?: Context) : { value: any, dependencies : Map<Hash, Dependency> } {
92+
literalize(path='', context?: Context) : HashedSetLiteral {
8893

8994
let dependencies = new Map<Hash, Dependency>();
9095

@@ -98,7 +103,7 @@ class HashedSet<T> {
98103
let elements = child.value;
99104
HashedObject.collectChildDeps(dependencies, path, child.dependencies, true);
100105

101-
let value = {_type: 'hashed_set', _hashes: hashes, _elements: elements};
106+
let value = {_type: 'hashed_set', _hashes: hashes, _elements: elements as any[]};
102107

103108
return { value: value, dependencies: dependencies};
104109
}
@@ -162,4 +167,5 @@ class HashedSet<T> {
162167

163168
}
164169

165-
export { HashedSet };
170+
export { HashedSet };
171+
export type { HashedSetLiteral };

src/data/model/mutable/MutableObject.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ type MutableObjectConfig = {supportsUndo?: boolean, supportsCheckpoints?: boolea
3232

3333
type StateCheckpoint = {
3434
mutableObject: Hash,
35-
terminalOpHashes: Array<Hash>,
36-
allAppliedOps: Array<Hash>,
35+
terminalOpHashes: Array<Hash>,
36+
allAppliedOps: Array<Hash>,
3737
activeCascInvsPerOp: Array<[Hash, Array<Hash>]>,
3838
exportedState: any
3939
};

src/util/arraymap.ts

+32
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,38 @@ class ArrayMap<K, V> {
8383
entries() {
8484
return this.inner.entries();
8585
}
86+
87+
toLiteral() {
88+
const result : {
89+
sorted: boolean,
90+
inner: {[key: keyof any]: V[]},
91+
size: number
92+
} = {
93+
sorted: this.sorted,
94+
inner: {},
95+
size: this.size
96+
};
97+
for (const [key, value] of this.inner) {
98+
if (typeof key !== "string" && typeof key !== "number" && typeof key !== "symbol") {
99+
throw new Error("This ArrayMap's key type isn't supported for literalization");
100+
}
101+
result.inner[key as keyof any] = value;
102+
}
103+
return result;
104+
}
105+
106+
static fromLiteral<K extends keyof any | string | number | symbol, V>(literal: {
107+
sorted: boolean,
108+
inner: {[key: keyof any]: V[]},
109+
size: number
110+
}) {
111+
const result = new ArrayMap<K, V>(literal.sorted);
112+
result.size = literal.size;
113+
for (const key in literal.inner) {
114+
result.inner.set(key as K, literal.inner[key]);
115+
}
116+
return result;
117+
}
86118
}
87119

88120
export { ArrayMap };

src/util/dedupmultimap.ts

+26
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,32 @@ class DedupMultiMap<K, V> {
6464
keys() {
6565
return this.inner.keys();
6666
}
67+
68+
toLiteral() {
69+
const result : {
70+
inner: {[key: keyof any]: V[]},
71+
} = {
72+
inner: {},
73+
};
74+
for (const [key, value] of this.inner.entries()) {
75+
if (typeof key !== "string" && typeof key !== "number" && typeof key !== "symbol") {
76+
throw new Error("ArrayMap key type isn't supported for literalization");
77+
}
78+
result.inner[key as keyof any | number | string | symbol] = Array.from(value.values());
79+
}
80+
return result;
81+
}
82+
83+
static fromLiteral<K extends keyof any | string | number | symbol, V>(literal: {
84+
inner: {[key: keyof any]: V[]},
85+
}) {
86+
const result = new DedupMultiMap<K, V>();
87+
for (const key in literal.inner) {
88+
const iter = literal.inner[key][Symbol.iterator]();
89+
result.inner.set(key as K, new HashedSet(iter));
90+
}
91+
return result;
92+
}
6793
}
6894

6995
export { DedupMultiMap };

test/data/checkpoints.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// import the mutable types
2+
import { MutableReference } from 'data/collections/mutable/MutableReference';
3+
import { MutableSet } from 'data/collections/mutable/MutableSet';
4+
import { MutableArray } from 'data/collections/mutable/MutableArray';
5+
// import { GrowOnlySet } from 'index';
6+
7+
describe('[CHK] Checkpoints', () => {
8+
it('[CHK01] Exporting and importing MutableReference', () => {
9+
const ref = new MutableReference();
10+
ref.setValue(1);
11+
expect(ref.getValue()).toBe(1);
12+
const refState = ref.exportMutableState();
13+
ref.setValue(2);
14+
ref.importMutableState(refState);
15+
expect(ref.getValue()).toBe(1);
16+
});
17+
18+
it('[CHK02] Exporting and importing MutableSet', () => {
19+
const set = new MutableSet<number>();
20+
set.add(1);
21+
expect([...set.values()]).toStrictEqual([1]);
22+
const setState = set.exportMutableState();
23+
set.add(2);
24+
set.importMutableState(setState);
25+
expect([...set.values()]).toStrictEqual([1]);
26+
});
27+
28+
it('[CHK03] Exporting and importing MutableArray', () => {
29+
const arr = new MutableArray<number>();
30+
arr.push(1);
31+
expect([...arr.values()]).toStrictEqual([1]);
32+
const arrState = arr.exportMutableState();
33+
arr.push(2);
34+
arr.importMutableState(arrState);
35+
expect([...arr.values()]).toStrictEqual([1]);
36+
});
37+
38+
})

0 commit comments

Comments
 (0)