|
@@ -5,13 +5,13 @@ import {permutations} from "../util/permutations";
|
|
|
import {bufferXOR} from "./buffer_xor";
|
|
|
import {PrimitiveDelta, Transaction} from "./delta";
|
|
|
|
|
|
-import * as _ from "lodash";
|
|
|
+// import * as _ from "lodash";
|
|
|
|
|
|
import {
|
|
|
Delta,
|
|
|
- isConflicting,
|
|
|
- iterMissingDependencies,
|
|
|
- iterConflicts,
|
|
|
+ // isConflicting,
|
|
|
+ // iterMissingDependencies,
|
|
|
+ // iterConflicts,
|
|
|
} from "./delta";
|
|
|
|
|
|
// The difference between two consecutive versions.
|
|
@@ -180,7 +180,7 @@ export class Version {
|
|
|
version.serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions);
|
|
|
const ovr = {};
|
|
|
for (const [key,val] of overridings.entries()) {
|
|
|
- ovr[key.getHash().toString('base64')] = val.getHash().toString('base64');
|
|
|
+ ovr[key.hash.toString('base64')] = val.hash.toString('base64');
|
|
|
}
|
|
|
embeddings.push({guestId, v: version.hash.toString('base64'), ovr});
|
|
|
}
|
|
@@ -204,7 +204,7 @@ export class Version {
|
|
|
versions.push({
|
|
|
id: this.hash.toString('base64'),
|
|
|
parent: parentVersion.hash.toString('base64'),
|
|
|
- delta: delta.getHash().toString('base64'),
|
|
|
+ delta: delta.hash.toString('base64'),
|
|
|
embeddings,
|
|
|
})
|
|
|
}
|
|
@@ -213,6 +213,16 @@ export class Version {
|
|
|
|
|
|
const initialHash = Buffer.alloc(32); // all zeros
|
|
|
|
|
|
+function isConflicting(deltaA: Delta, deltaB: Delta) {
|
|
|
+ // for performance, iterate over the delta that has the least conflicts:
|
|
|
+ if (deltaA.conflictsWith.length < deltaB.conflictsWith.length) {
|
|
|
+ return deltaA.conflictsWith.some(([d]) => d === deltaB);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return deltaB.conflictsWith.some(([d]) => d === deltaA);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
export class VersionRegistry {
|
|
|
readonly initialVersion: Version = new Version([], initialHash, 0, () => new Map());
|
|
|
|
|
@@ -245,15 +255,17 @@ export class VersionRegistry {
|
|
|
// then all of the guest versions' deltas must exist in the host version, or be explicitly overridden.
|
|
|
createVersion(parent: Version, delta: DiffType, embeddings: (Version) => Embeddings = () => new Map()): Version {
|
|
|
// Check pre-condition 1:
|
|
|
- const missingDependency = iterMissingDependencies(delta, parent).next().value;
|
|
|
- if (missingDependency !== undefined) {
|
|
|
- throw new Error("Missing dependency: " + missingDependency.description);
|
|
|
+ for (const [dep] of delta.getDependencies()) {
|
|
|
+ if (!parent.contains(dep)) {
|
|
|
+ throw new Error("createVersion: precondition failed: Missing dependency: " + dep.description);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// Check pre-condition 2:
|
|
|
- const conflictsWith = iterConflicts(delta, parent).next().value;
|
|
|
- if (conflictsWith !== undefined) {
|
|
|
- throw new Error("Delta " + delta.description + " conflicts with " + conflictsWith.description);
|
|
|
+ for (const [conflictingDelta] of delta.conflictsWith) {
|
|
|
+ if (parent.contains(conflictingDelta)) {
|
|
|
+ throw new Error("createVersion: precondition failed: Delta " + delta.description + " conflicts with " + conflictingDelta.description);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const newVersion = this.createVersionUnsafe(parent, delta, embeddings);
|
|
@@ -266,7 +278,7 @@ export class VersionRegistry {
|
|
|
for (const [_, guestDelta] of guestDiff) {
|
|
|
for (const guestPDelta of guestDelta.iterPrimitiveDeltas()) {
|
|
|
if (!primitiveDeltas.includes(overridings.get(guestPDelta) || guestPDelta)) {
|
|
|
- throw new Error("Guest's primitive delta " + guestPDelta.description + " does not occur in host, nor is it overridden.");
|
|
|
+ throw new Error("createVersion: precondition failed: Guest's primitive delta " + guestPDelta.description + " does not occur in host, nor is it overridden.");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -278,7 +290,7 @@ export class VersionRegistry {
|
|
|
// Faster than createVersion, but does not check pre-conditions.
|
|
|
// Idempotent
|
|
|
createVersionUnsafe(parent: Version, delta: DiffType, embeddings: (Version) => Embeddings = () => new Map()): Version {
|
|
|
- const newHash = bufferXOR(parent.hash, delta.getHash());
|
|
|
+ const newHash = bufferXOR(parent.hash, delta.hash);
|
|
|
// TODO: include embeddings in hash digest.
|
|
|
const existingVersion = this.lookupOptional(newHash);
|
|
|
if (existingVersion !== undefined) {
|
|
@@ -395,7 +407,6 @@ export class VersionRegistry {
|
|
|
ancestorsB.push(parent);
|
|
|
}
|
|
|
}
|
|
|
- throw new Error("Unexpected error: Versions have no LCA!")
|
|
|
}
|
|
|
|
|
|
getLCA(versions: Array<Version>): Version {
|
|
@@ -426,7 +437,7 @@ export class VersionRegistry {
|
|
|
}
|
|
|
|
|
|
const lca = this.getLCA(versions);
|
|
|
- printDebug("lca:", ...lca);
|
|
|
+ // printDebug("lca:", ...[...lca].map(d => d.description));
|
|
|
|
|
|
// Tuple
|
|
|
// Delta: a delta that is at least one of 'versions' but not in 'lca'
|
|
@@ -485,18 +496,18 @@ export class VersionRegistry {
|
|
|
// When nothing can be added anymore without introducing a conflict, we have a 'maximal version' (a result).
|
|
|
// It's possible that there is more than one 'maximal version'.
|
|
|
// Precondition: We assume that any single delta of 'deltasToTry' will not be conflicting with 'startVersion'.
|
|
|
- const depthFirst = (startVersion: Version, deltasToTry: Array<DeltaWithEmbedding>, depth: number) => {
|
|
|
+ const depthFirst = (startVersion: Version, candidates: Array<DeltaWithEmbedding>, depth: number) => {
|
|
|
function printIndent(...args) {
|
|
|
printDebug(" ".repeat(depth), ...args);
|
|
|
}
|
|
|
- // printIndent("deltasToTry=", ...deltasToTry);
|
|
|
+ printIndent("deltasToTry=", ...candidates.map(([d])=>d));
|
|
|
|
|
|
let couldNotRecurse = true;
|
|
|
|
|
|
- for (const [delta, guestDeltaMap] of deltasToTry) {
|
|
|
- const missingDependency = iterMissingDependencies(delta, startVersion).next().value; // just get the first element from the iterator
|
|
|
- if (missingDependency !== undefined) {
|
|
|
- // printIndent("missing dependency:", delta, "->", missingDependency)
|
|
|
+ for (const [delta, guestDeltaMap] of candidates) {
|
|
|
+ const haveMissingDependency = delta.getDependencies().some(([dependency]) => !startVersion.contains(dependency));
|
|
|
+ if (haveMissingDependency) {
|
|
|
+ printIndent("missing dependency, trying next delta")
|
|
|
continue; // skip this delta, but keep it in deltasToTry (its missing dependency may be in deltasToTry)
|
|
|
}
|
|
|
|
|
@@ -510,7 +521,7 @@ export class VersionRegistry {
|
|
|
const selfEmbeddingKeys = new Set<string>();
|
|
|
for (const [guestId, [guestDelta, overridings]] of guestDeltaMap.entries()) {
|
|
|
const {version: guestStartVersion} = startVersion.getEmbedding(guestId);
|
|
|
- console.log(guestId, "guestStartVersion: ", guestStartVersion);
|
|
|
+ // console.log(guestId, "guestStartVersion: ", guestStartVersion);
|
|
|
let nextGuestVersion;
|
|
|
if (guestDelta === null) {
|
|
|
nextGuestVersion = guestStartVersion;
|
|
@@ -525,6 +536,7 @@ export class VersionRegistry {
|
|
|
}
|
|
|
embeddings.set(guestId, {version: nextGuestVersion, overridings});
|
|
|
}
|
|
|
+ printIndent("Creating version (", delta, ...[...startVersion].map(d => d), ") with embeddings", embeddings);
|
|
|
const nextVersion = this.createVersion(startVersion, delta, newVersion => {
|
|
|
// add self-embeddings:
|
|
|
for (const guestId of selfEmbeddingKeys) {
|
|
@@ -532,16 +544,17 @@ export class VersionRegistry {
|
|
|
}
|
|
|
return embeddings;
|
|
|
});
|
|
|
- console.log("Created version ", nextVersion, " with embeddings", embeddings);
|
|
|
+ printIndent("Created version (", ...[...nextVersion].map(d => d), ") with embeddings", embeddings);
|
|
|
return nextVersion;
|
|
|
}
|
|
|
|
|
|
const nextVersion = createMergedVersionRecursive(startVersion, [delta, guestDeltaMap]);
|
|
|
|
|
|
// current delta does not have to be included again in next loop iterations
|
|
|
- deltasToTry = deltasToTry.filter(([d]) => d !== delta);
|
|
|
+ candidates = candidates.filter(([d]) => d !== delta);
|
|
|
|
|
|
- const [conflicting, nonConflicting] = _.partition(deltasToTry, ([d]) => isConflicting(d, delta));
|
|
|
+ // const [conflicting, nonConflicting] = _.partition(deltasToTry, ([d]) => isConflicting(d, delta));
|
|
|
+ const nonConflicting = candidates.filter(([candidate]) => !isConflicting(candidate, delta));
|
|
|
|
|
|
// if (conflicting.length > 0)
|
|
|
// printIndent("will be skipped (conflicts with", delta, "):", ...conflicting);
|
|
@@ -549,7 +562,7 @@ export class VersionRegistry {
|
|
|
depthFirst(nextVersion, nonConflicting, depth+1);
|
|
|
couldNotRecurse = false;
|
|
|
|
|
|
- if (conflicting.length === 0) {
|
|
|
+ if (nonConflicting.length === candidates.length) {
|
|
|
// all deltas from deltasToTry were included -> no need to try alternatives
|
|
|
break;
|
|
|
}
|
|
@@ -558,7 +571,7 @@ export class VersionRegistry {
|
|
|
if (couldNotRecurse) {
|
|
|
// possibly have a new maximal version
|
|
|
if (!result.some(v => startVersion.isSubSetOf(v))) {
|
|
|
- // printIndent("new result");
|
|
|
+ printIndent("new result");
|
|
|
result.push(startVersion);
|
|
|
}
|
|
|
}
|