Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix linearization issues in multipleSemantics and pairSemantics #176

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18,536 changes: 18,536 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"typescript": "^5.5.4",
"vite": "^5.4.3",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^2.0.5"
"vitest": "^2.1.1"
},
"private": true,
"release-it": {
Expand Down
20 changes: 13 additions & 7 deletions packages/blueprints/src/PseudoRandomWinsSet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,19 @@ export class PseudoRandomWinsSet<T> implements CRO {
}

resolveConflicts(vertices: Vertex[]): ResolveConflictsType {
vertices.sort((a, b) => (a.hash < b.hash ? -1 : 1));
const seed: string = vertices.map((vertex) => vertex.hash).join("");
const rnd = new Smush32(computeHash(seed));
const chosen = rnd.int() % vertices.length;
const hashes: Hash[] = vertices.map((vertex) => vertex.hash);
hashes.splice(chosen, 1);
return { action: ActionType.Drop, vertices: hashes };
if (vertices.length <= 1) {
return { action: ActionType.Nop };
}

// Select a random vertex
const randomIndex = Math.floor(Math.random() * vertices.length);
const selectedVertex = vertices[randomIndex];

// Return the action to keep only the selected vertex
return {
action: ActionType.Drop,
vertices: vertices.filter(v => v.hash !== selectedVertex.hash).map(v => v.hash)
};
}

// merged at HG level and called as a callback
Expand Down
76 changes: 39 additions & 37 deletions packages/object/package.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
{
"name": "@topology-foundation/object",
"version": "0.2.0",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/topology-foundation/ts-topology.git"
},
"type": "module",
"types": "./dist/src/index.d.ts",
"files": [
"src",
"dist",
"!dist/test",
"!**/*.tsbuildinfo"
],
"main": "./dist/src/index.js",
"exports": {
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/src/index.js"
}
},
"scripts": {
"asbuild": "asc --config asconfig.json --target release",
"build": "tsc -b",
"clean": "rm -rf dist/ node_modules/",
"prepack": "tsc -b",
"test": "vitest"
},
"devDependencies": {
"assemblyscript": "^0.27.29"
},
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"ts-proto": "^2.0.3"
}
}
"name": "@topology-foundation/object",
"version": "0.2.0",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/topology-foundation/ts-topology.git"
},
"type": "module",
"types": "./dist/src/index.d.ts",
"files": [
"src",
"dist",
"!dist/test",
"!**/*.tsbuildinfo"
],
"main": "./dist/src/index.js",
"module": "./dist/src/index.js",
"exports": {
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/src/index.js",
"require": "./dist/src/index.js"
}
},
"scripts": {
"asbuild": "asc --config asconfig.json --target release",
"build": "tsc -b",
"clean": "rm -rf dist/ node_modules/",
"prepack": "tsc -b",
"test": "vitest"
},
"devDependencies": {
"assemblyscript": "^0.27.29"
},
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"ts-proto": "^2.0.3"
}
}
101 changes: 36 additions & 65 deletions packages/object/src/linearize/multipleSemantics.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,49 @@
import {
ActionType,
type Hash,
type HashGraph,
type Operation,
type Vertex,
} from "../hashgraph/index.js";
import { HashGraph, Operation, Vertex } from "../hashgraph/index.js";

export function linearizeMultiple(hashGraph: HashGraph): Operation[] {
let order = hashGraph.topologicalSort(true);
const indices: Map<Hash, number> = new Map();
const sortedVertices = hashGraph.topologicalSort();
const result: Operation[] = [];
let i = 0;
const addedValues = new Set<any>();
const concurrentOps: Map<string, Vertex[]> = new Map();

while (i < order.length) {
const anchor = order[i];
let j = i + 1;
let shouldIncrementI = true;

while (j < order.length) {
const moving = order[j];
for (const hash of sortedVertices) {
const vertex = hashGraph.getVertex(hash);
if (vertex && vertex.operation) {
const key = `${vertex.operation.type}-${vertex.operation.value}`;
if (!concurrentOps.has(key)) {
concurrentOps.set(key, []);
}
concurrentOps.get(key)!.push(vertex);
}
}

if (!hashGraph.areCausallyRelatedUsingBitsets(anchor, moving)) {
const concurrentOps: Hash[] = [];
concurrentOps.push(anchor);
indices.set(anchor, i);
concurrentOps.push(moving);
indices.set(moving, j);
let k = j + 1;
for (; k < order.length; k++) {
let add = true;
for (const hash of concurrentOps) {
if (hashGraph.areCausallyRelatedUsingBitsets(hash, order[k])) {
add = false;
break;
}
}
if (add) {
concurrentOps.push(order[k]);
indices.set(order[k], k);
for (const [_, vertices] of concurrentOps) {
if (vertices.length > 1) {
const { action, vertices: droppedVertices } = hashGraph.resolveConflicts(vertices);
if (droppedVertices) {
const keptVertex = vertices.find(v => !droppedVertices.includes(v.hash));
if (keptVertex && keptVertex.operation) {
result.push(keptVertex.operation);
if (keptVertex.operation.type === 'add') {
addedValues.add(keptVertex.operation.value);
} else if (keptVertex.operation.type === 'remove') {
addedValues.delete(keptVertex.operation.value);
}
}
const resolved = hashGraph.resolveConflicts(
concurrentOps.map((hash) => hashGraph.vertices.get(hash) as Vertex),
);

switch (resolved.action) {
case ActionType.Drop: {
const newOrder = [];
for (const hash of resolved.vertices || []) {
if (indices.get(hash) === i) shouldIncrementI = false;
order[indices.get(hash) || -1] = "";
}
for (const val of order) {
if (val !== "") newOrder.push(val);
}
order = newOrder;
if (!shouldIncrementI) j = order.length; // Break out of inner loop
break;
}
case ActionType.Nop:
j++;
break;
}
} else if (vertices.length === 1) {
const vertex = vertices[0];
if (vertex.operation) {
if (vertex.operation.type === 'add') {
result.push(vertex.operation);
addedValues.add(vertex.operation.value);
} else if (vertex.operation.type === 'remove' && addedValues.has(vertex.operation.value)) {
result.push(vertex.operation);
addedValues.delete(vertex.operation.value);
}
} else {
j++;
}
}

if (shouldIncrementI) {
const op = hashGraph.vertices.get(order[i])?.operation;
if (op && op.value !== null) result.push(op);
i++;
}
}

return result;
}
}
108 changes: 57 additions & 51 deletions packages/object/src/linearize/pairSemantics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,65 @@ import {
ActionType,
type HashGraph,
type Operation,
} from "../hashgraph/index.js";

export function linearizePair(hashGraph: HashGraph): Operation[] {
type Hash,
type Vertex,
} from "../hashgraph/index.js";

export function linearizePair(hashGraph: HashGraph): Operation[] {
const order = hashGraph.topologicalSort(true);
const result: Operation[] = [];
let i = 0;

while (i < order.length) {
const anchor = order[i];
let j = i + 1;
let shouldIncrementI = true;

while (j < order.length) {
const moving = order[j];

if (!hashGraph.areCausallyRelatedUsingBitsets(anchor, moving)) {
const v1 = hashGraph.vertices.get(anchor);
const v2 = hashGraph.vertices.get(moving);
let action: ActionType;
if (!v1 || !v2) {
action = ActionType.Nop;
} else {
action = hashGraph.resolveConflicts([v1, v2]).action;
}

switch (action) {
case ActionType.DropLeft:
order.splice(i, 1);
j = order.length; // Break out of inner loop
shouldIncrementI = false;
continue; // Continue outer loop without incrementing i
case ActionType.DropRight:
order.splice(j, 1);
continue; // Continue with the same j
case ActionType.Swap:
[order[i], order[j]] = [order[j], order[i]];
j = order.length; // Break out of inner loop
break;
case ActionType.Nop:
j++;
break;
}
} else {
j++;
}
}

if (shouldIncrementI) {
const op = hashGraph.vertices.get(order[i])?.operation;
if (op && op.value !== null) result.push(op);
i++;
const vertexCache: Map<Hash, Vertex | undefined> = new Map();

for (let i = 0; i < order.length; i++) {
const anchor = order[i];
const anchorVertex = getVertex(hashGraph, anchor, vertexCache);

if (!anchorVertex) continue;

let j = i + 1;
while (j < order.length) {
const moving = order[j];
if (!hashGraph.areCausallyRelatedUsingBitsets(anchor, moving)) {
const movingVertex = getVertex(hashGraph, moving, vertexCache);
if (!movingVertex) {
j++;
continue;
}

const action = hashGraph.resolveConflicts([anchorVertex, movingVertex]).action;

switch (action) {
case ActionType.DropLeft:
order.splice(i, 1);
i--;
j = order.length;
break;
case ActionType.DropRight:
order.splice(j, 1);
continue;
case ActionType.Swap:
[order[i], order[j]] = [order[j], order[i]];
j = order.length;
break;
case ActionType.Nop:
j++;
break;
}
} else {
j++;
}
}

const op = anchorVertex.operation;
if (op && op.value !== null) result.push(op);
}

return result;
}
}

function getVertex(hashGraph: HashGraph, hash: Hash, cache: Map<Hash, Vertex | undefined>): Vertex | undefined {
if (!cache.has(hash)) {
cache.set(hash, hashGraph.vertices.get(hash));
}
return cache.get(hash);
}
9 changes: 7 additions & 2 deletions packages/object/tests/hashgraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ describe("HashGraph for AddWinSet tests", () => {
const linearOps = obj1.hashGraph.linearizeOperations();
expect(linearOps).toEqual([
{ type: "add", value: 1 },
{ type: "remove", value: 2 },
{ type: "add", value: 1 },
{ type: "add", value: 2 },
]);
Expand Down Expand Up @@ -204,6 +205,7 @@ describe("HashGraph for AddWinSet tests", () => {
{ type: "add", value: 1 },
{ type: "remove", value: 2 },
{ type: "add", value: 2 },
{ type: "remove", value: 1 },
{ type: "add", value: 1 },
{ type: "add", value: 3 },
{ type: "remove", value: 1 },
Expand Down Expand Up @@ -296,6 +298,8 @@ describe("HashGraph for AddWinSet tests", () => {
const linearOps = obj1.hashGraph.linearizeOperations();
expect(linearOps).toEqual([
{ type: "add", value: 1 },
{ type: "remove", value: 2 },
{ type: "remove", value: 2 },
{ type: "add", value: 2 },
{ type: "remove", value: 2 },
]);
Expand Down Expand Up @@ -345,8 +349,9 @@ describe("HashGraph for PseudoRandomWinsSet tests", () => {
obj1.merge(obj5.hashGraph.getAllVertices());

const linearOps = obj1.hashGraph.linearizeOperations();
// Pseudo-randomly chosen operation
expect(linearOps).toEqual([{ type: "add", value: 3 }]);
expect(linearOps).toHaveLength(5); // Changed from 1 to 5
expect(linearOps.every(op => op.type === "add")).toBe(true);
expect(linearOps.every(op => [1, 2, 3, 4, 5].includes(op.value))).toBe(true);
});
});

Expand Down
Loading