From 894646608d9096fdcc1e3f7853c43270da8e94cc Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Tue, 22 Aug 2023 19:37:35 +0200 Subject: [PATCH 01/18] stop waiting upon unlock --- src/graph.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graph.ts b/src/graph.ts index b189647..ca8cf93 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -41,6 +41,7 @@ class Lock { } unlock (byWhom: string) { + this.waiting.delete(byWhom) this.lockedBy.delete(byWhom) if (!this.isLocked()) { From b7a2ab6457b1467b11ce6a443a68428098be28dd Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Tue, 22 Aug 2023 20:26:54 +0200 Subject: [PATCH 02/18] move makeMakeLocker to Graferse class --- src/graph.test.ts | 37 +++---- src/graph.ts | 244 +++++++++++++++++++++++----------------------- 2 files changed, 138 insertions(+), 143 deletions(-) diff --git a/src/graph.test.ts b/src/graph.test.ts index 3c6571c..cb67a82 100644 --- a/src/graph.test.ts +++ b/src/graph.test.ts @@ -1,7 +1,7 @@ import type { Node } from 'ngraph.graph' import ngraphCreateGraph from 'ngraph.graph' import ngraphPath from 'ngraph.path' -import { makeMakeLocker, Graferse } from './graph.js' +import { Graferse } from './graph.js' import type { Lock, LinkLock, NextNode } from './graph.js' const getLockForLink = (from: Node, to: Node) => { @@ -94,8 +94,7 @@ describe('no dependencies', () => { lockToString.set(nodeX, 'nodeX') lockToString.set(nodeY, 'nodeY') - const makeLocker = makeMakeLocker( - creator, + const makeLocker = creator.makeMakeLocker( node => node, getLockForLink, lock => lockToString.get(lock) as string// what we are going to give current nodes in @@ -169,7 +168,7 @@ describe('no dependencies', () => { const path2 = [nodeX, nodeB, nodeY] - const makeLocker = makeMakeLocker(creator, node => node, getLockForLink, node => node) + const makeLocker = creator.makeMakeLocker(node => node, getLockForLink, node => node) var forwardPath1: Array> = [] var forwardPath2: Array> = [] @@ -241,8 +240,7 @@ describe('ngraph', () => { const path = pathFinder.find('a', 'c').reverse() var forwardPath: Array> = [] - const makeLocker = makeMakeLocker>( - creator, + const makeLocker = creator.makeMakeLocker>( node => node.data, getLockForLink, node => node.id as string)("agent1").makePathLocker @@ -295,7 +293,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() - const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink, node => node.id as string)("agent1").makePathLocker + const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string)("agent1").makePathLocker var forwardPath: Array> = [] const locker = makeLocker(path)((nextNodes) => { forwardPath = nextNodes }) const arrivedAt = (nodeId: string) => @@ -344,7 +342,7 @@ describe('ngraph', () => { const path = pathFinder.find('a', 'c').reverse() var forwardPath: Array> = [] - const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink, node => node.id as string)("agent1").makePathLocker + const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string)("agent1").makePathLocker const locker = makeLocker(path)((nextNodes) => { forwardPath = nextNodes }) const arrivedAt = (nodeId: string) => locker.arrivedAt(path.findIndex(node => node.id === nodeId)) @@ -394,7 +392,7 @@ describe('ngraph', () => { var s1ForwardPath: Array> = [] var s2ForwardPath: Array> = [] - const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) const s2LockNext = makeLocker("agent2").makePathLocker(s2Path)((nextNodes) => { s2ForwardPath = nextNodes }) @@ -498,8 +496,7 @@ describe('ngraph', () => { const s1Path = pathFinder.find('a', 'h').reverse() const s2Path = pathFinder.find('b', 'i').reverse() - const makeLocker = makeMakeLocker>( - creator, + const makeLocker = creator.makeMakeLocker>( node => node.data, getLockForLink, x => x.id as string @@ -685,7 +682,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const s1Path = pathFinder.find('a', 'e').reverse() - const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) var s1ForwardPath: Array> = [] const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) @@ -799,7 +796,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const s1Path = pathFinder.find('a', 'e').reverse() - const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) var s1ForwardPath: Array> = [] const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) @@ -917,7 +914,7 @@ describe('ngraph', () => { const path1 = pathFinder.find('a', 'z').reverse() const path2 = pathFinder.find('g', 'y').reverse() - const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) var nextNodes1: Array> = [] var nextNodes2: Array> = [] const agent1at = makeLocker("agent1").makePathLocker(path1)((nn) => { nextNodes1 = nn }).lockNext @@ -1018,7 +1015,7 @@ describe('ngraph', () => { const path1 = pathFinder.find('a', 'z').reverse() const path2 = pathFinder.find('g', 'y').reverse() - const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) var nextNodes1: Array> = [] var nextNodes2: Array> = [] const agent1at = makeLocker("agent1").makePathLocker(path1)((nn) => { nextNodes1 = nn }).lockNext @@ -1082,7 +1079,7 @@ describe('ngraph', () => { // const s1Path = pathFinder.find('a', 'd') // const s2Path = pathFinder.find('b', 'c') - // const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink) + // const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink) // var s1ForwardPath: Array> = [] // var s2ForwardPath: Array> = [] // const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }).lockNext @@ -1140,7 +1137,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() - const makeLocker = makeMakeLocker>(creator, node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) const lockNext = makeLocker("agent1").makePathLocker(path)((nextNodes) => {}) for (var i = 0; i < path.length; i++) { @@ -1244,8 +1241,7 @@ describe('Exceptions', () => { const nodeC = creator.makeLock() const nodeX = creator.makeLock() - const makeLocker = makeMakeLocker( - creator, + const makeLocker = creator.makeMakeLocker( node => node, (from, to) => creator.makeLinkLock(), node => node) @@ -1287,8 +1283,7 @@ describe('Exceptions', () => { const nodeC = creator.makeLock() const nodeX = creator.makeLock() - const makeLocker = makeMakeLocker( - creator, + const makeLocker = creator.makeMakeLocker( node => node, (from, to) => creator.makeLinkLock(), node => node) diff --git a/src/graph.ts b/src/graph.ts index ca8cf93..921629b 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -125,6 +125,7 @@ class LinkLock { } } +type NextNode = { node: U, index: number } // TODO add a keep alive where owners need to report in periodically, else their locks will be freed class Graferse { @@ -199,148 +200,147 @@ class Graferse } return true } -} -type NextNode = { node: U, index: number } -function makeMakeLocker ( - creator: Graferse, - getLock: (x: T) => Lock, // given a T, gives you a Lock - getLockForLink: (from: T, to: T) => LinkLock, - identity: (x: T) => U, // returns external node identity -) { - type NextNodes = (nextNodes: NextNode[], remaining: number) => void - return (byWhom: string) => { - const makePathLocker = (path: T[]) => (callback: NextNodes) => { - // given an index in the path, tries to lock all bidirectional edges - // till the last node in the path - // returns false if first edge fails, otherwise returns true - // as we can proceed some of the way in the same direction - function tryLockAllBidirectionalEdges(subpath: T[]) { - if (subpath.length < 2) { - return true - } - // TODO will these locks and unlocks trigger waiters? - // may need a cangetlock? function. prepare lock? - const linkLock = getLockForLink(subpath[0], subpath[1]) - const desc = `from ${identity(subpath[0])} to ${identity(subpath[1])}` - const fromNodeId = JSON.stringify(identity(subpath[0])) - if (!linkLock.isBidirectional) { - console.debug(` ok - ${desc} not bidirectional`) - return true - } + makeMakeLocker ( + getLock: (x: T) => Lock, // given a T, gives you a Lock + getLockForLink: (from: T, to: T) => LinkLock, + identity: (x: T) => U, // returns external node identity + ) { + type NextNodes = (nextNodes: NextNode[], remaining: number) => void + return (byWhom: string) => { + const makePathLocker = (path: T[]) => (callback: NextNodes) => { + // given an index in the path, tries to lock all bidirectional edges + // till the last node in the path + // returns false if first edge fails, otherwise returns true + // as we can proceed some of the way in the same direction + function tryLockAllBidirectionalEdges(subpath: T[]) { + if (subpath.length < 2) { + return true + } + // TODO will these locks and unlocks trigger waiters? + // may need a cangetlock? function. prepare lock? + const linkLock = getLockForLink(subpath[0], subpath[1]) + const desc = `from ${identity(subpath[0])} to ${identity(subpath[1])}` + const fromNodeId = JSON.stringify(identity(subpath[0])) + if (!linkLock.isBidirectional) { + console.debug(` ok - ${desc} not bidirectional`) + return true + } - const linkLockResult = linkLock.requestLock(byWhom, fromNodeId) + const linkLockResult = linkLock.requestLock(byWhom, fromNodeId) - // if it failed to lock because of opposing direction - if (linkLockResult === "CON") { - console.debug(` fail - ${desc} locked against us`) - return false - } - - if (!tryLockAllBidirectionalEdges(subpath.slice(1))) { - linkLock.unlock(byWhom, fromNodeId) - return false - } + // if it failed to lock because of opposing direction + if (linkLockResult === "CON") { + console.debug(` fail - ${desc} locked against us`) + return false + } - console.debug(` ok - ${desc} obtained`) - return true - } + if (!tryLockAllBidirectionalEdges(subpath.slice(1))) { + linkLock.unlock(byWhom, fromNodeId) + return false + } - const clearAllPathLocks = () => { - debug(`── clearAllPathLocks | ${byWhom} ──`); - const whoCanMoveNow = new Set() - for (let i = 0; i < path.length; i++) { - whoCanMoveNow.addAll(getLock(path[i]).unlock(byWhom)) - if (i < path.length -1) // except the last node - whoCanMoveNow.addAll(getLockForLink(path[i], path[i+1]).unlock(byWhom)) + console.debug(` ok - ${desc} obtained`) + return true } - creator.notifyWaiters(whoCanMoveNow) - } - const lockNext = (currentNode: U) => { - console.warn("lockNext is deprecated, please use arrivedAt") - const currentIdx = path.findIndex(node => identity(node) === currentNode) - if (currentIdx === -1) { - console.error(` You're claiming to be at a node not on your path`) - console.error(` Couldnt find "${currentNode}" in ${JSON.stringify(path.map(identity))}`) - clearAllPathLocks() - return + const clearAllPathLocks = () => { + debug(`── clearAllPathLocks | ${byWhom} ──`); + const whoCanMoveNow = new Set() + for (let i = 0; i < path.length; i++) { + whoCanMoveNow.addAll(getLock(path[i]).unlock(byWhom)) + if (i < path.length -1) // except the last node + whoCanMoveNow.addAll(getLockForLink(path[i], path[i+1]).unlock(byWhom)) + } + this.notifyWaiters(whoCanMoveNow) } - arrivedAt(currentIdx) - } - - const arrivedAt = (currentIdx: number) => { - debug(`┌─ Lock | ${byWhom} ${currentIdx} ${identity(path[currentIdx])} ──`); - creator.lastCallCache.set(byWhom, () => arrivedAt(currentIdx)) - - const beforeCount = 0, afterCount = 1 - const lastIdx = path.length -1 - const firstToLock = Math.max(currentIdx - beforeCount, 0) // first to be locked - const lastToLock = Math.min(currentIdx + afterCount, lastIdx) // last to be locked - const whoCanMoveNow = new Set() - - const nextNodes: NextNode[] = [] - // go through path from start to last node to be locked - for (let i = 0; i <= lastToLock; i++) { - // unlock all edges before current position - if (i > 0 && i <= currentIdx) { - const fromNodeId = JSON.stringify(identity(path[i-1])) - whoCanMoveNow.addAll(getLockForLink(path[i-1], path[i]).unlock(byWhom, fromNodeId)) + const lockNext = (currentNode: U) => { + console.warn("lockNext is deprecated, please use arrivedAt") + const currentIdx = path.findIndex(node => identity(node) === currentNode) + if (currentIdx === -1) { + console.error(` You're claiming to be at a node not on your path`) + console.error(` Couldnt find "${currentNode}" in ${JSON.stringify(path.map(identity))}`) + clearAllPathLocks() + return } + arrivedAt(currentIdx) + } - // if its behind the firstToLock, unlock it - if (i < firstToLock) { - whoCanMoveNow.addAll(getLock(path[i]).unlock(byWhom)) - debug(`unlocked ${identity(path[i])} for ${byWhom}`) - continue + const arrivedAt = (currentIdx: number) => { + debug(`┌─ Lock | ${byWhom} ${currentIdx} ${identity(path[currentIdx])} ──`); + this.lastCallCache.set(byWhom, () => arrivedAt(currentIdx)) + + + const beforeCount = 0, afterCount = 1 + const lastIdx = path.length -1 + const firstToLock = Math.max(currentIdx - beforeCount, 0) // first to be locked + const lastToLock = Math.min(currentIdx + afterCount, lastIdx) // last to be locked + const whoCanMoveNow = new Set() + + const nextNodes: NextNode[] = [] + // go through path from start to last node to be locked + for (let i = 0; i <= lastToLock; i++) { + // unlock all edges before current position + if (i > 0 && i <= currentIdx) { + const fromNodeId = JSON.stringify(identity(path[i-1])) + whoCanMoveNow.addAll(getLockForLink(path[i-1], path[i]).unlock(byWhom, fromNodeId)) + } + + // if its behind the firstToLock, unlock it + if (i < firstToLock) { + whoCanMoveNow.addAll(getLock(path[i]).unlock(byWhom)) + debug(`unlocked ${identity(path[i])} for ${byWhom}`) + continue + } + + const lock = getLock(path[i]) + if (!this.isLockGroupAvailable(lock, byWhom)) { + debug("Could not obtain lock, group is locked") + break; + } + /* Lock from firstToLock to lastToLock */ + // if failed to obtain lock, dont try to get any more + if (!lock.requestLock(byWhom, JSON.stringify(identity(path[i])))) { + break; + } + debug(" trying to lock bidir edges from node %o", identity(path[i])) + // TODO consider returning the length of obtained edge locks + // if its > 0, even though further failed, allow the againt to retain the node lock + // so we can enter corridors as far as we can and wait there + if (!tryLockAllBidirectionalEdges(path.slice(i))) { + // unlock previously obtained node lock + lock.unlock(byWhom) + break + } + nextNodes.push({node: identity(path[i]), index: i}) } - const lock = getLock(path[i]) - if (!creator.isLockGroupAvailable(lock, byWhom)) { - debug("Could not obtain lock, group is locked") - break; - } - /* Lock from firstToLock to lastToLock */ - // if failed to obtain lock, dont try to get any more - if (!lock.requestLock(byWhom, JSON.stringify(identity(path[i])))) { - break; - } - debug(" trying to lock bidir edges from node %o", identity(path[i])) - // TODO consider returning the length of obtained edge locks - // if its > 0, even though further failed, allow the againt to retain the node lock - // so we can enter corridors as far as we can and wait there - if (!tryLockAllBidirectionalEdges(path.slice(i))) { - // unlock previously obtained node lock - lock.unlock(byWhom) - break - } - nextNodes.push({node: identity(path[i]), index: i}) - } + debug({whoCanMoveNow}) + // TODO consider not calling back with same values as last time or leave it up to clients to handle this + callback( + nextNodes, + path.length - (currentIdx +1) + ) - debug({whoCanMoveNow}) - // TODO consider not calling back with same values as last time or leave it up to clients to handle this - callback( - nextNodes, - path.length - (currentIdx +1) - ) + this.notifyWaiters(whoCanMoveNow) + debug('└────\n') + } - creator.notifyWaiters(whoCanMoveNow) - debug('└────\n') + return { + lockNext, + arrivedAt, + clearAllPathLocks, + } } - return { - lockNext, - arrivedAt, - clearAllPathLocks, + makePathLocker, + clearAllLocks: () => this.clearAllLocks(byWhom) } } - return { - makePathLocker, - clearAllLocks: () => creator.clearAllLocks(byWhom) - } } + } -export { makeMakeLocker, Graferse } +export { Graferse } export type { Lock, LinkLock, NextNode } From 488e4a851c19b105d64040269f036a22acd802a8 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Tue, 22 Aug 2023 20:54:36 +0200 Subject: [PATCH 03/18] move identity to constructor --- src/graph.test.ts | 87 +++++++++++++++++++++++------------------------ src/graph.ts | 34 ++++++++++-------- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/src/graph.test.ts b/src/graph.test.ts index cb67a82..cf4d324 100644 --- a/src/graph.test.ts +++ b/src/graph.test.ts @@ -11,7 +11,7 @@ const getLockForLink = (from: Node, to: Node) => { describe('Graferse class', () => { test('creating locks', () => { - const creator = new Graferse() + const creator = new Graferse(node => node) expect(creator.locks).toEqual([]) expect(creator.linkLocks).toEqual([]) @@ -26,7 +26,7 @@ describe('Graferse class', () => { expect(creator.linkLocks).toEqual([linkLock1]) }) test('lock groups', () => { - const creator = new Graferse() + const creator = new Graferse(node => node) const lock1 = creator.makeLock() const lock2 = creator.makeLock() creator.setLockGroup([lock1, lock2]) @@ -43,7 +43,7 @@ describe('Graferse class', () => { expect(lock1.unlock("agent1")).toEqual(new Set(["agent2"])) }) test('clearAllLocks', () => { - const creator = new Graferse() + const creator = new Graferse(node => node) const lock1 = creator.makeLock() const lock2 = creator.makeLock() const linkLock1 = creator.makeLinkLock(true) @@ -77,7 +77,9 @@ describe('no dependencies', () => { const getLockForLink = (from: Lock, to: Lock) => { return creator.makeLinkLock() } - const creator = new Graferse() + const creator = new Graferse( + lock => lockToString.get(lock) as string// what we are going to give current nodes in + ) const nodeA = creator.makeLock() const nodeB = creator.makeLock() const nodeC = creator.makeLock() @@ -94,10 +96,9 @@ describe('no dependencies', () => { lockToString.set(nodeX, 'nodeX') lockToString.set(nodeY, 'nodeY') - const makeLocker = creator.makeMakeLocker( + const makeLocker = creator.makeMakeLocker( node => node, getLockForLink, - lock => lockToString.get(lock) as string// what we are going to give current nodes in ) const forwardPaths1: Array>> = [] @@ -157,7 +158,7 @@ describe('no dependencies', () => { const getLockForLink = (from: Lock, to: Lock) => { return creator.makeLinkLock() } - const creator = new Graferse() + const creator = new Graferse(node => node) const nodeA = creator.makeLock() const nodeB = creator.makeLock() const nodeC = creator.makeLock() @@ -168,7 +169,7 @@ describe('no dependencies', () => { const path2 = [nodeX, nodeB, nodeY] - const makeLocker = creator.makeMakeLocker(node => node, getLockForLink, node => node) + const makeLocker = creator.makeMakeLocker(node => node, getLockForLink) var forwardPath1: Array> = [] var forwardPath2: Array> = [] @@ -227,7 +228,7 @@ describe('no dependencies', () => { describe('ngraph', () => { test('basic locking', () => { const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -240,10 +241,9 @@ describe('ngraph', () => { const path = pathFinder.find('a', 'c').reverse() var forwardPath: Array> = [] - const makeLocker = creator.makeMakeLocker>( + const makeLocker = creator.makeMakeLocker( node => node.data, - getLockForLink, - node => node.id as string)("agent1").makePathLocker + getLockForLink)("agent1").makePathLocker const locker = makeLocker(path)((nextNodes) => { forwardPath = nextNodes }) const arrivedAt = (nodeId: string) => locker.arrivedAt(path.findIndex(node => node.id === nodeId)) @@ -281,7 +281,7 @@ describe('ngraph', () => { test('basic locking - clearAllPathLocks', () => { const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -293,7 +293,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() - const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string)("agent1").makePathLocker + const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink)("agent1").makePathLocker var forwardPath: Array> = [] const locker = makeLocker(path)((nextNodes) => { forwardPath = nextNodes }) const arrivedAt = (nodeId: string) => @@ -329,7 +329,7 @@ describe('ngraph', () => { test('unexpected queue jumping', () => { const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -342,7 +342,7 @@ describe('ngraph', () => { const path = pathFinder.find('a', 'c').reverse() var forwardPath: Array> = [] - const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string)("agent1").makePathLocker + const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink)("agent1").makePathLocker const locker = makeLocker(path)((nextNodes) => { forwardPath = nextNodes }) const arrivedAt = (nodeId: string) => locker.arrivedAt(path.findIndex(node => node.id === nodeId)) @@ -368,7 +368,7 @@ describe('ngraph', () => { test('two robot mutual exclusion', () => { const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -392,7 +392,7 @@ describe('ngraph', () => { var s1ForwardPath: Array> = [] var s2ForwardPath: Array> = [] - const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) const s2LockNext = makeLocker("agent2").makePathLocker(s2Path)((nextNodes) => { s2ForwardPath = nextNodes }) @@ -446,7 +446,7 @@ describe('ngraph', () => { test('bidirectional corridor convoy', () => { const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(x => x.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -496,10 +496,9 @@ describe('ngraph', () => { const s1Path = pathFinder.find('a', 'h').reverse() const s2Path = pathFinder.find('b', 'i').reverse() - const makeLocker = creator.makeMakeLocker>( + const makeLocker = creator.makeMakeLocker( node => node.data, getLockForLink, - x => x.id as string ) const s1NextPaths: Array>> = [] const s2NextPaths: Array>> = [] @@ -652,7 +651,7 @@ describe('ngraph', () => { test('bidirectional corridor with early exit', () => { const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -682,7 +681,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const s1Path = pathFinder.find('a', 'e').reverse() - const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) var s1ForwardPath: Array> = [] const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) @@ -764,7 +763,7 @@ describe('ngraph', () => { test('three agents bidirectional corridor with early exit', () => { const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -796,7 +795,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const s1Path = pathFinder.find('a', 'e').reverse() - const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) var s1ForwardPath: Array> = [] const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) @@ -867,7 +866,7 @@ describe('ngraph', () => { // v // Z const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -914,7 +913,7 @@ describe('ngraph', () => { const path1 = pathFinder.find('a', 'z').reverse() const path2 = pathFinder.find('g', 'y').reverse() - const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) var nextNodes1: Array> = [] var nextNodes2: Array> = [] const agent1at = makeLocker("agent1").makePathLocker(path1)((nn) => { nextNodes1 = nn }).lockNext @@ -967,7 +966,7 @@ describe('ngraph', () => { // v // Z const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) const nodeA = graph.addNode('a', creator.makeLock()) const nodeB = graph.addNode('b', creator.makeLock()) @@ -1015,7 +1014,7 @@ describe('ngraph', () => { const path1 = pathFinder.find('a', 'z').reverse() const path2 = pathFinder.find('g', 'y').reverse() - const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) var nextNodes1: Array> = [] var nextNodes2: Array> = [] const agent1at = makeLocker("agent1").makePathLocker(path1)((nn) => { nextNodes1 = nn }).lockNext @@ -1124,7 +1123,7 @@ describe('ngraph', () => { //}) test('directed', () => { const graph = ngraphCreateGraph() - const creator = new Graferse() + const creator = new Graferse>(node => node.id as string) graph.addNode('a', creator.makeLock()) graph.addNode('b', creator.makeLock()) @@ -1137,7 +1136,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() - const makeLocker = creator.makeMakeLocker>(node => node.data, getLockForLink, node => node.id as string) + const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) const lockNext = makeLocker("agent1").makePathLocker(path)((nextNodes) => {}) for (var i = 0; i < path.length; i++) { @@ -1149,7 +1148,7 @@ describe('ngraph', () => { describe('Components', () => { describe('Lock', () => { test('locking twice', () => { - const creator = new Graferse() + const creator = new Graferse(node => node) const lock = creator.makeLock() expect(lock.requestLock('test', 'abc')).toBeTruthy() expect(lock.requestLock('test', 'def')).toBeTruthy() @@ -1160,7 +1159,7 @@ describe('Components', () => { test('locking when directed edge', () => { const logSpyWarn = jest.spyOn(console, 'warn').mockImplementation() const logSpyError = jest.spyOn(console, 'error').mockImplementation() - const creator = new Graferse() + const creator = new Graferse(node => node) const linkLock = creator.makeLinkLock() // by default is directed edge expect(logSpyWarn).not.toHaveBeenCalled() expect(logSpyError).not.toHaveBeenCalled() @@ -1175,20 +1174,20 @@ describe('Components', () => { describe('Locking in both directions', () => { test('single owner can lock both directions', () => { - const creator = new Graferse() + const creator = new Graferse(node => node) const linkLock = creator.makeLinkLock(true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent1', 'down')).toEqual("FREE") }) test('owner cannot lock both directions if multiple owners', () => { - const creator = new Graferse() + const creator = new Graferse(node => node) const linkLock = creator.makeLinkLock(true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent2', 'up')).toEqual("PRO") expect(linkLock.requestLock('agent1', 'down')).toEqual("CON") }) test('agent cannot lock if both directions already locked', () => { - const creator = new Graferse() + const creator = new Graferse(node => node) const linkLock = creator.makeLinkLock(true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent1', 'down')).toEqual("FREE") @@ -1208,7 +1207,7 @@ describe('Components', () => { describe('Listeners', () => { test('smoke', () => { let listenCallbackCounter = 0 - const creator = new Graferse() + const creator = new Graferse(node => node) creator.addListener(() => { listenCallbackCounter++ }) // no one has been called yet @@ -1235,16 +1234,15 @@ describe('Exceptions', () => { const getLockForLink = (from: Lock, to: Lock) => { return creator.makeLinkLock() } - const creator = new Graferse() + const creator = new Graferse(node => node) const nodeA = creator.makeLock() const nodeB = creator.makeLock() const nodeC = creator.makeLock() const nodeX = creator.makeLock() - const makeLocker = creator.makeMakeLocker( + const makeLocker = creator.makeMakeLocker( node => node, - (from, to) => creator.makeLinkLock(), - node => node) + (from, to) => creator.makeLinkLock()) const test1Path = [nodeA, nodeB, nodeC] const test1At = makeLocker("test1").makePathLocker(test1Path)( @@ -1277,16 +1275,15 @@ describe('Exceptions', () => { const getLockForLink = (from: Lock, to: Lock) => { return creator.makeLinkLock() } - const creator = new Graferse() + const creator = new Graferse(node => node) const nodeA = creator.makeLock() const nodeB = creator.makeLock() const nodeC = creator.makeLock() const nodeX = creator.makeLock() - const makeLocker = creator.makeMakeLocker( + const makeLocker = creator.makeMakeLocker( node => node, - (from, to) => creator.makeLinkLock(), - node => node) + (from, to) => creator.makeLinkLock()) const test1Path = [nodeA, nodeB, nodeC] var forwardPath: Array> = [] diff --git a/src/graph.ts b/src/graph.ts index 921629b..e474722 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -127,13 +127,20 @@ class LinkLock { type NextNode = { node: U, index: number } // TODO add a keep alive where owners need to report in periodically, else their locks will be freed -class Graferse +class Graferse { locks: Lock[] = [] linkLocks: LinkLock[] = [] lockGroups: Lock[][] = [] lastCallCache = new Map void>() listeners: Array<() => void> = [] + identity: (x: T) => U + + constructor( + identity: (x: T) => U, // returns external node identity + ) { + this.identity = identity + } makeLock() { const lock = new Lock() @@ -201,10 +208,9 @@ class Graferse return true } - makeMakeLocker ( + makeMakeLocker ( getLock: (x: T) => Lock, // given a T, gives you a Lock getLockForLink: (from: T, to: T) => LinkLock, - identity: (x: T) => U, // returns external node identity ) { type NextNodes = (nextNodes: NextNode[], remaining: number) => void return (byWhom: string) => { @@ -213,15 +219,15 @@ class Graferse // till the last node in the path // returns false if first edge fails, otherwise returns true // as we can proceed some of the way in the same direction - function tryLockAllBidirectionalEdges(subpath: T[]) { + const tryLockAllBidirectionalEdges = (subpath: T[]) => { if (subpath.length < 2) { return true } // TODO will these locks and unlocks trigger waiters? // may need a cangetlock? function. prepare lock? const linkLock = getLockForLink(subpath[0], subpath[1]) - const desc = `from ${identity(subpath[0])} to ${identity(subpath[1])}` - const fromNodeId = JSON.stringify(identity(subpath[0])) + const desc = `from ${this.identity(subpath[0])} to ${this.identity(subpath[1])}` + const fromNodeId = JSON.stringify(this.identity(subpath[0])) if (!linkLock.isBidirectional) { console.debug(` ok - ${desc} not bidirectional`) return true @@ -257,10 +263,10 @@ class Graferse const lockNext = (currentNode: U) => { console.warn("lockNext is deprecated, please use arrivedAt") - const currentIdx = path.findIndex(node => identity(node) === currentNode) + const currentIdx = path.findIndex(node => this.identity(node) === currentNode) if (currentIdx === -1) { console.error(` You're claiming to be at a node not on your path`) - console.error(` Couldnt find "${currentNode}" in ${JSON.stringify(path.map(identity))}`) + console.error(` Couldnt find "${currentNode}" in ${JSON.stringify(path.map(this.identity))}`) clearAllPathLocks() return } @@ -268,7 +274,7 @@ class Graferse } const arrivedAt = (currentIdx: number) => { - debug(`┌─ Lock | ${byWhom} ${currentIdx} ${identity(path[currentIdx])} ──`); + debug(`┌─ Lock | ${byWhom} ${currentIdx} ${this.identity(path[currentIdx])} ──`); this.lastCallCache.set(byWhom, () => arrivedAt(currentIdx)) @@ -283,14 +289,14 @@ class Graferse for (let i = 0; i <= lastToLock; i++) { // unlock all edges before current position if (i > 0 && i <= currentIdx) { - const fromNodeId = JSON.stringify(identity(path[i-1])) + const fromNodeId = JSON.stringify(this.identity(path[i-1])) whoCanMoveNow.addAll(getLockForLink(path[i-1], path[i]).unlock(byWhom, fromNodeId)) } // if its behind the firstToLock, unlock it if (i < firstToLock) { whoCanMoveNow.addAll(getLock(path[i]).unlock(byWhom)) - debug(`unlocked ${identity(path[i])} for ${byWhom}`) + debug(`unlocked ${this.identity(path[i])} for ${byWhom}`) continue } @@ -301,10 +307,10 @@ class Graferse } /* Lock from firstToLock to lastToLock */ // if failed to obtain lock, dont try to get any more - if (!lock.requestLock(byWhom, JSON.stringify(identity(path[i])))) { + if (!lock.requestLock(byWhom, JSON.stringify(this.identity(path[i])))) { break; } - debug(" trying to lock bidir edges from node %o", identity(path[i])) + debug(" trying to lock bidir edges from node %o", this.identity(path[i])) // TODO consider returning the length of obtained edge locks // if its > 0, even though further failed, allow the againt to retain the node lock // so we can enter corridors as far as we can and wait there @@ -313,7 +319,7 @@ class Graferse lock.unlock(byWhom) break } - nextNodes.push({node: identity(path[i]), index: i}) + nextNodes.push({node: this.identity(path[i]), index: i}) } debug({whoCanMoveNow}) From c777b6d8e1d5ba441050472353cd3e038564dc92 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Tue, 22 Aug 2023 21:33:35 +0200 Subject: [PATCH 04/18] add id to locks --- src/graph.test.ts | 192 ++++++++++++++++++++++++---------------------- src/graph.ts | 11 ++- 2 files changed, 109 insertions(+), 94 deletions(-) diff --git a/src/graph.test.ts b/src/graph.test.ts index cf4d324..c432e75 100644 --- a/src/graph.test.ts +++ b/src/graph.test.ts @@ -15,7 +15,7 @@ describe('Graferse class', () => { expect(creator.locks).toEqual([]) expect(creator.linkLocks).toEqual([]) - const lock1 = creator.makeLock() + const lock1 = creator.makeLock('lock1') expect(typeof lock1).toBe("object") expect(creator.locks).toEqual([lock1]) expect(creator.linkLocks).toEqual([]) @@ -27,8 +27,8 @@ describe('Graferse class', () => { }) test('lock groups', () => { const creator = new Graferse(node => node) - const lock1 = creator.makeLock() - const lock2 = creator.makeLock() + const lock1 = creator.makeLock('lock1') + const lock2 = creator.makeLock('lock2') creator.setLockGroup([lock1, lock2]) expect(lock1.requestLock("agent1", "lock1")).toBeTruthy() @@ -44,8 +44,8 @@ describe('Graferse class', () => { }) test('clearAllLocks', () => { const creator = new Graferse(node => node) - const lock1 = creator.makeLock() - const lock2 = creator.makeLock() + const lock1 = creator.makeLock('lock1') + const lock2 = creator.makeLock('lock2') const linkLock1 = creator.makeLinkLock(true) const linkLock2 = creator.makeLinkLock(true) @@ -80,13 +80,13 @@ describe('no dependencies', () => { const creator = new Graferse( lock => lockToString.get(lock) as string// what we are going to give current nodes in ) - const nodeA = creator.makeLock() - const nodeB = creator.makeLock() - const nodeC = creator.makeLock() + const nodeA = creator.makeLock('nodeA') + const nodeB = creator.makeLock('nodeB') + const nodeC = creator.makeLock('nodeC') const path1 = [nodeA, nodeB, nodeC] - const nodeX = creator.makeLock() - const nodeY = creator.makeLock() + const nodeX = creator.makeLock('nodeX') + const nodeY = creator.makeLock('nodeY') const path2 = [nodeX, nodeB, nodeY] const lockToString = new Map() @@ -159,13 +159,13 @@ describe('no dependencies', () => { return creator.makeLinkLock() } const creator = new Graferse(node => node) - const nodeA = creator.makeLock() - const nodeB = creator.makeLock() - const nodeC = creator.makeLock() + const nodeA = creator.makeLock('nodeA') + const nodeB = creator.makeLock('nodeB') + const nodeC = creator.makeLock('nodeC') const path1 = [nodeA, nodeB, nodeC] - const nodeX = creator.makeLock() - const nodeY = creator.makeLock() + const nodeX = creator.makeLock('nodeX') + const nodeY = creator.makeLock('nodeY') const path2 = [nodeX, nodeB, nodeY] @@ -230,12 +230,13 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') - graph.addLink('a', 'b', creator.makeLock()) - graph.addLink('b', 'c', creator.makeLock()) + graph.addLink('a', 'b', creator.makeLock('ab')) + graph.addLink('b', 'c', creator.makeLock('bc')) const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() @@ -283,12 +284,13 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') - graph.addLink('a', 'b', creator.makeLock()) - graph.addLink('b', 'c', creator.makeLock()) + graph.addLink('a', 'b', creator.makeLock('ab')) + graph.addLink('b', 'c', creator.makeLock('bc')) const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() @@ -331,12 +333,13 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') - graph.addLink('a', 'b', creator.makeLock()) - graph.addLink('b', 'c', creator.makeLock()) + graph.addLink('a', 'b', creator.makeLock('ab')) + graph.addLink('b', 'c', creator.makeLock('bc')) const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() @@ -370,10 +373,11 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) - const nodeD = graph.addNode('d', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') + const nodeD = makeNode('d') // A // \ @@ -382,9 +386,9 @@ describe('ngraph', () => { // ^ // / // B - graph.addLink('a', 'c', creator.makeLock()) - graph.addLink('b', 'c', creator.makeLock()) - graph.addLink('c', 'd', creator.makeLock()) + graph.addLink('a', 'c', creator.makeLock('ac')) + graph.addLink('b', 'c', creator.makeLock('bc')) + graph.addLink('c', 'd', creator.makeLock('cd')) const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const s1Path = pathFinder.find('a', 'd').reverse() @@ -448,15 +452,16 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(x => x.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) - const nodeD = graph.addNode('d', creator.makeLock()) - const nodeE = graph.addNode('e', creator.makeLock()) - const nodeF = graph.addNode('f', creator.makeLock()) - const nodeG = graph.addNode('g', creator.makeLock()) - const nodeH = graph.addNode('h', creator.makeLock()) - const nodeI = graph.addNode('i', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') + const nodeD = makeNode('d') + const nodeE = makeNode('e') + const nodeF = makeNode('f') + const nodeG = makeNode('g') + const nodeH = makeNode('h') + const nodeI = makeNode('i') // B H // \ ^ @@ -653,11 +658,12 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) - const nodeD = graph.addNode('d', creator.makeLock()) - const nodeE = graph.addNode('e', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') + const nodeD = makeNode('d') + const nodeE = makeNode('e') // A <----> B <----> C <----> D // \ @@ -765,12 +771,13 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) - const nodeD = graph.addNode('d', creator.makeLock()) - const nodeE = graph.addNode('e', creator.makeLock()) - const nodeF = graph.addNode('f', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') + const nodeD = makeNode('d') + const nodeE = makeNode('e') + const nodeF = makeNode('f') // F // ^ @@ -868,15 +875,16 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) - const nodeD = graph.addNode('d', creator.makeLock()) - const nodeE = graph.addNode('e', creator.makeLock()) - const nodeF = graph.addNode('f', creator.makeLock()) - const nodeG = graph.addNode('g', creator.makeLock()) - const nodeY = graph.addNode('y', creator.makeLock()) - const nodeZ = graph.addNode('z', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') + const nodeD = makeNode('d') + const nodeE = makeNode('e') + const nodeF = makeNode('f') + const nodeG = makeNode('g') + const nodeY = makeNode('y') + const nodeZ = makeNode('z') const lockCD = creator.makeLinkLock(true) const lockDE = creator.makeLinkLock(true) @@ -968,15 +976,16 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - const nodeA = graph.addNode('a', creator.makeLock()) - const nodeB = graph.addNode('b', creator.makeLock()) - const nodeC = graph.addNode('c', creator.makeLock()) - const nodeD = graph.addNode('d', creator.makeLock()) - const nodeE = graph.addNode('e', creator.makeLock()) - const nodeF = graph.addNode('f', creator.makeLock()) - const nodeG = graph.addNode('g', creator.makeLock()) - const nodeY = graph.addNode('y', creator.makeLock()) - const nodeZ = graph.addNode('z', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') + const nodeD = makeNode('d') + const nodeE = makeNode('e') + const nodeF = makeNode('f') + const nodeG = makeNode('g') + const nodeY = makeNode('y') + const nodeZ = makeNode('z') const lockCD = creator.makeLinkLock(true) const lockDE = creator.makeLinkLock(true) @@ -1125,13 +1134,14 @@ describe('ngraph', () => { const graph = ngraphCreateGraph() const creator = new Graferse>(node => node.id as string) - graph.addNode('a', creator.makeLock()) - graph.addNode('b', creator.makeLock()) - graph.addNode('c', creator.makeLock()) - graph.addLink('a', 'b', creator.makeLock()) - graph.addLink('b', 'c', creator.makeLock()) - graph.addLink('c', 'b', creator.makeLock()) - graph.addLink('b', 'a', creator.makeLock()) + const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) + const nodeA = makeNode('a') + const nodeB = makeNode('b') + const nodeC = makeNode('c') + graph.addLink('a', 'b', creator.makeLock('ab')) + graph.addLink('b', 'c', creator.makeLock('bc')) + graph.addLink('c', 'b', creator.makeLock('cb')) + graph.addLink('b', 'a', creator.makeLock('ba')) const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() @@ -1149,7 +1159,7 @@ describe('Components', () => { describe('Lock', () => { test('locking twice', () => { const creator = new Graferse(node => node) - const lock = creator.makeLock() + const lock = creator.makeLock('lock') expect(lock.requestLock('test', 'abc')).toBeTruthy() expect(lock.requestLock('test', 'def')).toBeTruthy() }) @@ -1235,10 +1245,10 @@ describe('Exceptions', () => { return creator.makeLinkLock() } const creator = new Graferse(node => node) - const nodeA = creator.makeLock() - const nodeB = creator.makeLock() - const nodeC = creator.makeLock() - const nodeX = creator.makeLock() + const nodeA = creator.makeLock('nodeA') + const nodeB = creator.makeLock('nodeB') + const nodeC = creator.makeLock('nodeC') + const nodeX = creator.makeLock('nodeX') const makeLocker = creator.makeMakeLocker( node => node, @@ -1276,10 +1286,10 @@ describe('Exceptions', () => { return creator.makeLinkLock() } const creator = new Graferse(node => node) - const nodeA = creator.makeLock() - const nodeB = creator.makeLock() - const nodeC = creator.makeLock() - const nodeX = creator.makeLock() + const nodeA = creator.makeLock('nodeA') + const nodeB = creator.makeLock('nodeB') + const nodeC = creator.makeLock('nodeC') + const nodeX = creator.makeLock('nodeX') const makeLocker = creator.makeMakeLocker( node => node, diff --git a/src/graph.ts b/src/graph.ts index e474722..e9afd38 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -14,9 +14,14 @@ Set.prototype.addAll = function(s) { } class Lock { + id: string lockedBy: Set = new Set() waiting: Set = new Set() + constructor(id: string) { + this.id = id + } + requestLock (byWhom: string, what: string) { this.waiting.delete(byWhom) if (!this.isLocked()) { @@ -68,7 +73,7 @@ class Lock { type LinkLockType = "FREE" | "PRO" | "CON" class LinkLock { - private _lock: Lock = new Lock() + private _lock: Lock = new Lock("linklock") private _directions: Set = new Set() readonly isBidirectional: boolean @@ -142,8 +147,8 @@ class Graferse this.identity = identity } - makeLock() { - const lock = new Lock() + makeLock(id: string) { + const lock = new Lock(id) this.locks.push(lock) return lock } From cbda59d576277d648678999b60d1819f831a2c51 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Tue, 22 Aug 2023 21:43:45 +0200 Subject: [PATCH 05/18] move unlock debug to lock --- src/graph.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/graph.ts b/src/graph.ts index e9afd38..b1b1f1d 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -46,8 +46,13 @@ class Lock { } unlock (byWhom: string) { - this.waiting.delete(byWhom) - this.lockedBy.delete(byWhom) + if (this.lockedBy.delete(byWhom)) { + debug(`unlocked ${this.id} for ${byWhom}`) + } + + if (this.waiting.delete(byWhom)) { + debug(`stopped waiting ${this.id} for ${byWhom}`) + } if (!this.isLocked()) { // no guarentee that this resource is obtainable by any of the waiters @@ -301,7 +306,6 @@ class Graferse // if its behind the firstToLock, unlock it if (i < firstToLock) { whoCanMoveNow.addAll(getLock(path[i]).unlock(byWhom)) - debug(`unlocked ${this.identity(path[i])} for ${byWhom}`) continue } From d49a4800bf6e0d1be4362720d080e5434c58de04 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Wed, 20 Sep 2023 18:46:30 +0200 Subject: [PATCH 06/18] ensure we wait for the other direction --- src/graph.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/graph.ts b/src/graph.ts index b1b1f1d..9f03061 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -96,8 +96,11 @@ class LinkLock { // already locked by me if (this._lock.isLocked(byWhom)) { if (this._directions.size === 1 && !this._directions.has(direction)) { - if (this._lock.isLockedByOtherThan(byWhom)) + if (this._lock.isLockedByOtherThan(byWhom)) { + debug(`Resource 'link from ${direction}' is locked, ${byWhom} will wait`) + this._lock.waiting.add(byWhom) return "CON" + } this._directions.add(direction) } return "FREE" From 369f6d78c681b49c1817159c462b67e9e2c1e94d Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Wed, 20 Sep 2023 19:07:17 +0200 Subject: [PATCH 07/18] Revert "ensure we wait for the other direction" This reverts commit 5856608c6baf5876df326d28dda44a4f81c69242. --- src/graph.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/graph.ts b/src/graph.ts index 9f03061..b1b1f1d 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -96,11 +96,8 @@ class LinkLock { // already locked by me if (this._lock.isLocked(byWhom)) { if (this._directions.size === 1 && !this._directions.has(direction)) { - if (this._lock.isLockedByOtherThan(byWhom)) { - debug(`Resource 'link from ${direction}' is locked, ${byWhom} will wait`) - this._lock.waiting.add(byWhom) + if (this._lock.isLockedByOtherThan(byWhom)) return "CON" - } this._directions.add(direction) } return "FREE" From 4c942f50d089364c30ce6af0b750c4933b139ede Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 07:41:15 +0200 Subject: [PATCH 08/18] log details when locked against us --- src/graph.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/graph.ts b/src/graph.ts index b1b1f1d..745c77c 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -86,6 +86,13 @@ class LinkLock { this.isBidirectional = isBidirectional // only relevent for edges } + getDetails() { + return { + directions: this._directions, + who: this._lock.lockedBy, + } + } + requestLock (byWhom: string, direction: string) { if (!this.isBidirectional) { console.error("Who is trying to lock a non-bidir link?", {byWhom, direction}) @@ -247,7 +254,8 @@ class Graferse // if it failed to lock because of opposing direction if (linkLockResult === "CON") { - console.debug(` fail - ${desc} locked against us`) + console.warn(` fail - ${desc} locked against us`) + console.warn(linkLock.getDetails()) return false } From 61072f56bd13c6f5ce94af7af8c1af52870b2757 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 07:45:25 +0200 Subject: [PATCH 09/18] incase we unlock already held locks from previous traversal --- src/graph.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graph.ts b/src/graph.ts index 745c77c..a7a9951 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -333,7 +333,7 @@ class Graferse // so we can enter corridors as far as we can and wait there if (!tryLockAllBidirectionalEdges(path.slice(i))) { // unlock previously obtained node lock - lock.unlock(byWhom) + whoCanMoveNow.addAll(lock.unlock(byWhom)) break } nextNodes.push({node: this.identity(path[i]), index: i}) From 7cb92683319a96711b580e7386900152d03fee54 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 07:51:50 +0200 Subject: [PATCH 10/18] clear both directions when not specified --- src/graph.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/graph.ts b/src/graph.ts index a7a9951..8fc30f9 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -126,14 +126,19 @@ class LinkLock { } unlock (byWhom: string, direction?: string) { - if (direction) { - if (this._lock.isLocked(byWhom) && !this._lock.isLockedByOtherThan(byWhom)) { + // if its locked only by a single robot + if (this._lock.isLocked(byWhom) && !this._lock.isLockedByOtherThan(byWhom)) { + if (direction) { this._directions.delete(direction) + // if we still are holding one direction, dont release lock if (this._directions.size > 0) return + } else { + this._directions.clear() } } + return this._lock.unlock(byWhom) } From 382745e695d14e3df346ded5d05542ea20ce7a29 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 07:53:33 +0200 Subject: [PATCH 11/18] dont stringify strings --- src/graph.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/graph.ts b/src/graph.ts index 8fc30f9..7ca423e 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -13,6 +13,12 @@ Set.prototype.addAll = function(s) { } } +function stringify(x: any) { + return typeof x === 'string' + ? x + : JSON.stringify(x) +} + class Lock { id: string lockedBy: Set = new Set() @@ -249,7 +255,7 @@ class Graferse // may need a cangetlock? function. prepare lock? const linkLock = getLockForLink(subpath[0], subpath[1]) const desc = `from ${this.identity(subpath[0])} to ${this.identity(subpath[1])}` - const fromNodeId = JSON.stringify(this.identity(subpath[0])) + const fromNodeId = stringify(this.identity(subpath[0])) if (!linkLock.isBidirectional) { console.debug(` ok - ${desc} not bidirectional`) return true @@ -289,7 +295,7 @@ class Graferse const currentIdx = path.findIndex(node => this.identity(node) === currentNode) if (currentIdx === -1) { console.error(` You're claiming to be at a node not on your path`) - console.error(` Couldnt find "${currentNode}" in ${JSON.stringify(path.map(this.identity))}`) + console.error(` Couldnt find "${currentNode}" in ${stringify(path.map(this.identity))}`) clearAllPathLocks() return } @@ -312,7 +318,7 @@ class Graferse for (let i = 0; i <= lastToLock; i++) { // unlock all edges before current position if (i > 0 && i <= currentIdx) { - const fromNodeId = JSON.stringify(this.identity(path[i-1])) + const fromNodeId = stringify(this.identity(path[i-1])) whoCanMoveNow.addAll(getLockForLink(path[i-1], path[i]).unlock(byWhom, fromNodeId)) } @@ -329,7 +335,7 @@ class Graferse } /* Lock from firstToLock to lastToLock */ // if failed to obtain lock, dont try to get any more - if (!lock.requestLock(byWhom, JSON.stringify(this.identity(path[i])))) { + if (!lock.requestLock(byWhom, stringify(this.identity(path[i])))) { break; } debug(" trying to lock bidir edges from node %o", this.identity(path[i])) From 2728cfc125a9f13c6e6df213fde4d10e00194bf7 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Wed, 20 Sep 2023 20:59:45 +0200 Subject: [PATCH 12/18] extract OnewayLinkLock --- src/graph.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/graph.ts b/src/graph.ts index 7ca423e..db8127b 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -86,11 +86,7 @@ type LinkLockType = "FREE" | "PRO" | "CON" class LinkLock { private _lock: Lock = new Lock("linklock") private _directions: Set = new Set() - readonly isBidirectional: boolean - - constructor(isBidirectional: boolean = false) { - this.isBidirectional = isBidirectional // only relevent for edges - } + readonly isBidirectional: boolean = true getDetails() { return { @@ -99,13 +95,7 @@ class LinkLock { } } - requestLock (byWhom: string, direction: string) { - if (!this.isBidirectional) { - console.error("Who is trying to lock a non-bidir link?", {byWhom, direction}) - console.warn("This will cause problems because it should stop locking here") - return "FREE" - } - + requestLock (byWhom: string, direction: string): LinkLockType { // already locked by me if (this._lock.isLocked(byWhom)) { if (this._directions.size === 1 && !this._directions.has(direction)) { @@ -153,6 +143,15 @@ class LinkLock { } } +class OnewayLinkLock extends LinkLock { + readonly isBidirectional = false + requestLock (byWhom: string, direction: string): LinkLockType { + console.error("Who is trying to lock a non-bidir link?", {byWhom, direction}) + console.warn("This will cause problems because it should stop locking here") + return "FREE" + } +} + type NextNode = { node: U, index: number } // TODO add a keep alive where owners need to report in periodically, else their locks will be freed class Graferse @@ -177,7 +176,10 @@ class Graferse } makeLinkLock(isBidirectional: boolean = false) { - const linkLock = new LinkLock(isBidirectional) + const linkLock = isBidirectional + ? new LinkLock() + : new OnewayLinkLock() + this.linkLocks.push(linkLock) return linkLock } From 7d61d23e3f83dd46d6c1c8d65ba4ea29df3241c0 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Wed, 20 Sep 2023 21:03:21 +0200 Subject: [PATCH 13/18] remove isBidirectional --- src/graph.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/graph.ts b/src/graph.ts index db8127b..11a190e 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -86,7 +86,6 @@ type LinkLockType = "FREE" | "PRO" | "CON" class LinkLock { private _lock: Lock = new Lock("linklock") private _directions: Set = new Set() - readonly isBidirectional: boolean = true getDetails() { return { @@ -144,7 +143,6 @@ class LinkLock { } class OnewayLinkLock extends LinkLock { - readonly isBidirectional = false requestLock (byWhom: string, direction: string): LinkLockType { console.error("Who is trying to lock a non-bidir link?", {byWhom, direction}) console.warn("This will cause problems because it should stop locking here") @@ -258,7 +256,7 @@ class Graferse const linkLock = getLockForLink(subpath[0], subpath[1]) const desc = `from ${this.identity(subpath[0])} to ${this.identity(subpath[1])}` const fromNodeId = stringify(this.identity(subpath[0])) - if (!linkLock.isBidirectional) { + if (linkLock instanceof OnewayLinkLock) { console.debug(` ok - ${desc} not bidirectional`) return true } From aa64031929de892144c00d5af58958490fdc56f8 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 08:08:59 +0200 Subject: [PATCH 14/18] introduce allowed directions --- src/graph.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/graph.ts b/src/graph.ts index 11a190e..0d12462 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -86,6 +86,11 @@ type LinkLockType = "FREE" | "PRO" | "CON" class LinkLock { private _lock: Lock = new Lock("linklock") private _directions: Set = new Set() + private _allowed_directions: Set + + constructor (to: string, from: string) { + this._allowed_directions = new Set([to, from]) + } getDetails() { return { @@ -175,8 +180,8 @@ class Graferse makeLinkLock(isBidirectional: boolean = false) { const linkLock = isBidirectional - ? new LinkLock() - : new OnewayLinkLock() + ? new LinkLock("up", "down") + : new OnewayLinkLock("up", "down") this.linkLocks.push(linkLock) return linkLock From 4b85f11de530f6cff5bb4aa21ba4ae60271ce308 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 08:12:42 +0200 Subject: [PATCH 15/18] enforce direction checking --- src/graph.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/graph.ts b/src/graph.ts index 0d12462..a787901 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -88,6 +88,11 @@ class LinkLock { private _directions: Set = new Set() private _allowed_directions: Set + check (direction: string) { + if (!this._allowed_directions.has(direction)) + throw new Error(`no such direction ${direction}`) + } + constructor (to: string, from: string) { this._allowed_directions = new Set([to, from]) } @@ -100,6 +105,7 @@ class LinkLock { } requestLock (byWhom: string, direction: string): LinkLockType { + this.check(direction) // already locked by me if (this._lock.isLocked(byWhom)) { if (this._directions.size === 1 && !this._directions.has(direction)) { @@ -126,6 +132,7 @@ class LinkLock { } unlock (byWhom: string, direction?: string) { + if (direction) this.check(direction) // if its locked only by a single robot if (this._lock.isLocked(byWhom) && !this._lock.isLockedByOtherThan(byWhom)) { if (direction) { From 9d373840d4b67373f620c72e327dcfee35fbed32 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 08:58:56 +0200 Subject: [PATCH 16/18] pass end node ids --- src/graph.test.ts | 130 +++++++++++++++++++++++----------------------- src/graph.ts | 10 ++-- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/src/graph.test.ts b/src/graph.test.ts index c432e75..58c729d 100644 --- a/src/graph.test.ts +++ b/src/graph.test.ts @@ -20,7 +20,7 @@ describe('Graferse class', () => { expect(creator.locks).toEqual([lock1]) expect(creator.linkLocks).toEqual([]) - const linkLock1 = creator.makeLinkLock() + const linkLock1 = creator.makeLinkLock('a', 'b') expect(typeof linkLock1).toBe("object") expect(creator.locks).toEqual([lock1]) expect(creator.linkLocks).toEqual([linkLock1]) @@ -46,8 +46,8 @@ describe('Graferse class', () => { const creator = new Graferse(node => node) const lock1 = creator.makeLock('lock1') const lock2 = creator.makeLock('lock2') - const linkLock1 = creator.makeLinkLock(true) - const linkLock2 = creator.makeLinkLock(true) + const linkLock1 = creator.makeLinkLock('up', 'down', true) + const linkLock2 = creator.makeLinkLock('up', 'down', true) expect(lock1.requestLock("agent1", "lock1")).toBeTruthy() expect(lock2.requestLock("agent2", "lock2")).toBeTruthy() @@ -75,7 +75,7 @@ describe('Graferse class', () => { describe('no dependencies', () => { test('basic locking with identities', () => { const getLockForLink = (from: Lock, to: Lock) => { - return creator.makeLinkLock() + return creator.makeLinkLock(from.id, to.id) } const creator = new Graferse( lock => lockToString.get(lock) as string// what we are going to give current nodes in @@ -156,9 +156,9 @@ describe('no dependencies', () => { test('basic locking', () => { const getLockForLink = (from: Lock, to: Lock) => { - return creator.makeLinkLock() + return creator.makeLinkLock(from.id, to.id) } - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id) const nodeA = creator.makeLock('nodeA') const nodeB = creator.makeLock('nodeB') const nodeC = creator.makeLock('nodeC') @@ -171,8 +171,8 @@ describe('no dependencies', () => { const makeLocker = creator.makeMakeLocker(node => node, getLockForLink) - var forwardPath1: Array> = [] - var forwardPath2: Array> = [] + let forwardPath1: Array> = [] + let forwardPath2: Array> = [] const test1At = makeLocker("test1").makePathLocker(path1)( (nextNodes) => { forwardPath1 = nextNodes } @@ -186,20 +186,20 @@ describe('no dependencies', () => { expect(forwardPath2).toEqual([]) test1At.arrivedAt(path1.indexOf(nodeA)) - expect(forwardPath1).toEqual([{index: 0, node: nodeA}, {index: 1, node: nodeB}]) + expect(forwardPath1).toEqual([{index: 0, node: 'nodeA'}, {index: 1, node: 'nodeB'}]) expect(forwardPath2).toEqual([]) test2At.arrivedAt(path2.indexOf(nodeX)) - expect(forwardPath1).toEqual([{index: 0, node: nodeA}, {index: 1, node: nodeB}]) - expect(forwardPath2).toEqual([{index: 0, node: nodeX}]) // only nodeX because nodeB is locked + expect(forwardPath1).toEqual([{index: 0, node: 'nodeA'}, {index: 1, node: 'nodeB'}]) + expect(forwardPath2).toEqual([{index: 0, node: 'nodeX'}]) // only nodeX because nodeB is locked test1At.arrivedAt(path1.indexOf(nodeB)) - expect(forwardPath1).toEqual([{index: 1, node: nodeB}, {index: 2, node: nodeC}]) - expect(forwardPath2).toEqual([{index: 0, node: nodeX}]) // only nodeX because nodeB is still locked + expect(forwardPath1).toEqual([{index: 1, node: 'nodeB'}, {index: 2, node: 'nodeC'}]) + expect(forwardPath2).toEqual([{index: 0, node: 'nodeX'}]) // only nodeX because nodeB is still locked test1At.arrivedAt(path1.indexOf(nodeC)) - expect(forwardPath1).toEqual([{index: 2, node: nodeC}]) - expect(forwardPath2).toEqual([{index: 0, node: nodeX}, {index: 1, node: nodeB}]) // nodeB is now unlocked + expect(forwardPath1).toEqual([{index: 2, node: 'nodeC'}]) + expect(forwardPath2).toEqual([{index: 0, node: 'nodeX'}, {index: 1, node: 'nodeB'}]) // nodeB is now unlocked test1At.clearAllPathLocks() //expect(forwardPath1).toEqual([]) @@ -472,10 +472,10 @@ describe('ngraph', () => { // A I // bidirectional locks - const lockCD = creator.makeLinkLock(true) - const lockDE = creator.makeLinkLock(true) - const lockEF = creator.makeLinkLock(true) - const lockFG = creator.makeLinkLock(true) + const lockCD = creator.makeLinkLock('c', 'd', true) + const lockDE = creator.makeLinkLock('d', 'e', true) + const lockEF = creator.makeLinkLock('e', 'f', true) + const lockFG = creator.makeLinkLock('f', 'g', true) // bidirectional links const linkCD = graph.addLink('c', 'd', lockCD) @@ -492,10 +492,10 @@ describe('ngraph', () => { // directed links - const linkAC = graph.addLink('a', 'c', creator.makeLinkLock()) - const linkBC = graph.addLink('b', 'c', creator.makeLinkLock()) - const linkGH = graph.addLink('g', 'h', creator.makeLinkLock()) - const linkGI = graph.addLink('g', 'i', creator.makeLinkLock()) + const linkAC = graph.addLink('a', 'c', creator.makeLinkLock('a', 'c')) + const linkBC = graph.addLink('b', 'c', creator.makeLinkLock('b', 'c')) + const linkGH = graph.addLink('g', 'h', creator.makeLinkLock('g', 'h')) + const linkGI = graph.addLink('g', 'i', creator.makeLinkLock('g', 'i')) const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const s1Path = pathFinder.find('a', 'h').reverse() @@ -670,9 +670,9 @@ describe('ngraph', () => { // v // E - const lockAB = creator.makeLinkLock(true) - const lockBC = creator.makeLinkLock(true) - const lockCD = creator.makeLinkLock(true) + const lockAB = creator.makeLinkLock('a', 'b', true) + const lockBC = creator.makeLinkLock('b', 'c', true) + const lockCD = creator.makeLinkLock('c', 'd', true) const linkAB = graph.addLink('a', 'b', lockAB) const linkBC = graph.addLink('b', 'c', lockBC) @@ -682,7 +682,7 @@ describe('ngraph', () => { const linkCB = graph.addLink('c', 'b', lockBC) const linkBA = graph.addLink('b', 'a', lockAB) - const linkCE = graph.addLink('c', 'e', creator.makeLinkLock()) + const linkCE = graph.addLink('c', 'e', creator.makeLinkLock('c', 'e')) const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const s1Path = pathFinder.find('a', 'e').reverse() @@ -787,17 +787,17 @@ describe('ngraph', () => { // v // E - const lockAB = creator.makeLinkLock(true) - const lockBC = creator.makeLinkLock(true) + const lockAB = creator.makeLinkLock('a', 'b', true) + const lockBC = creator.makeLinkLock('b', 'c', true) const linkAB = graph.addLink('a', 'b', lockAB) const linkBC = graph.addLink('b', 'c', lockBC) const linkCB = graph.addLink('c', 'b', lockBC) const linkBA = graph.addLink('b', 'a', lockAB) - const linkCD = graph.addLink('d', 'c', creator.makeLinkLock()) - const linkCE = graph.addLink('c', 'e', creator.makeLinkLock()) - const linkCF = graph.addLink('c', 'f', creator.makeLinkLock()) + const linkCD = graph.addLink('d', 'c', creator.makeLinkLock('d', 'c')) + const linkCE = graph.addLink('c', 'e', creator.makeLinkLock('c', 'e')) + const linkCF = graph.addLink('c', 'f', creator.makeLinkLock('c', 'f')) const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const s1Path = pathFinder.find('a', 'e').reverse() @@ -886,10 +886,10 @@ describe('ngraph', () => { const nodeY = makeNode('y') const nodeZ = makeNode('z') - const lockCD = creator.makeLinkLock(true) - const lockDE = creator.makeLinkLock(true) - const lockCY = creator.makeLinkLock(true) - const lockEZ = creator.makeLinkLock(true) + const lockCD = creator.makeLinkLock('c', 'd', true) + const lockDE = creator.makeLinkLock('d', 'e', true) + const lockCY = creator.makeLinkLock('c', 'y', true) + const lockEZ = creator.makeLinkLock('e', 'z', true) function addBiLink(a: string, b: string, lock: LinkLock) { return [ @@ -898,12 +898,12 @@ describe('ngraph', () => { ] } - graph.addLink('a', 'b', creator.makeLinkLock()), - graph.addLink('b', 'c', creator.makeLinkLock()), + graph.addLink('a', 'b', creator.makeLinkLock('a', 'b')), + graph.addLink('b', 'c', creator.makeLinkLock('b', 'c')), addBiLink('c', 'd', lockCD) addBiLink('d', 'e', lockDE) - graph.addLink('g', 'f', creator.makeLinkLock()), - graph.addLink('f', 'e', creator.makeLinkLock()), + graph.addLink('g', 'f', creator.makeLinkLock('g', 'f')), + graph.addLink('f', 'e', creator.makeLinkLock('f', 'e')), addBiLink('c', 'y', lockCY) addBiLink('e', 'z', lockEZ) @@ -987,11 +987,11 @@ describe('ngraph', () => { const nodeY = makeNode('y') const nodeZ = makeNode('z') - const lockCD = creator.makeLinkLock(true) - const lockDE = creator.makeLinkLock(true) - const lockEF = creator.makeLinkLock(true) - const lockCY = creator.makeLinkLock(true) - const lockEZ = creator.makeLinkLock(true) + const lockCD = creator.makeLinkLock('c', 'd', true) + const lockDE = creator.makeLinkLock('d', 'e', true) + const lockEF = creator.makeLinkLock('e', 'f', true) + const lockCY = creator.makeLinkLock('c', 'y', true) + const lockEZ = creator.makeLinkLock('e', 'z', true) function addBiLink(a: string, b: string, lock: LinkLock) { return [ @@ -1000,11 +1000,11 @@ describe('ngraph', () => { ] } - graph.addLink('a', 'b', creator.makeLinkLock()), - graph.addLink('b', 'c', creator.makeLinkLock()), + graph.addLink('a', 'b', creator.makeLinkLock('a', 'b')), + graph.addLink('b', 'c', creator.makeLinkLock('b', 'c')), addBiLink('c', 'd', lockCD) addBiLink('d', 'e', lockDE) - graph.addLink('g', 'f', creator.makeLinkLock()), + graph.addLink('g', 'f', creator.makeLinkLock('g', 'f')), addBiLink('f', 'e', lockEF), addBiLink('c', 'y', lockCY) addBiLink('e', 'z', lockEZ) @@ -1079,9 +1079,9 @@ describe('ngraph', () => { // // ----> C ----> D // // / // // B - // graph.addLink('a', 'c', creator.makeLinkLock()) - // graph.addLink('b', 'c', creator.makeLinkLock()) - // graph.addLink('c', 'd', creator.makeLinkLock()) + // graph.addLink('a', 'c', creator.makeLinkLock('a', 'c')) + // graph.addLink('b', 'c', creator.makeLinkLock('b', 'c')) + // graph.addLink('c', 'd', creator.makeLinkLock('c', 'd')) // const pathFinder = ngraphPath.aStar(graph, { oriented: true }) // const s1Path = pathFinder.find('a', 'd') @@ -1170,7 +1170,7 @@ describe('Components', () => { const logSpyWarn = jest.spyOn(console, 'warn').mockImplementation() const logSpyError = jest.spyOn(console, 'error').mockImplementation() const creator = new Graferse(node => node) - const linkLock = creator.makeLinkLock() // by default is directed edge + const linkLock = creator.makeLinkLock('up', 'down') // by default is directed edge expect(logSpyWarn).not.toHaveBeenCalled() expect(logSpyError).not.toHaveBeenCalled() expect(linkLock.requestLock('test', 'up')).toEqual("FREE") @@ -1185,20 +1185,20 @@ describe('Components', () => { describe('Locking in both directions', () => { test('single owner can lock both directions', () => { const creator = new Graferse(node => node) - const linkLock = creator.makeLinkLock(true) // is bidirectional + const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent1', 'down')).toEqual("FREE") }) test('owner cannot lock both directions if multiple owners', () => { const creator = new Graferse(node => node) - const linkLock = creator.makeLinkLock(true) // is bidirectional + const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent2', 'up')).toEqual("PRO") expect(linkLock.requestLock('agent1', 'down')).toEqual("CON") }) test('agent cannot lock if both directions already locked', () => { const creator = new Graferse(node => node) - const linkLock = creator.makeLinkLock(true) // is bidirectional + const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent1', 'down')).toEqual("FREE") expect(linkLock.requestLock('agent2', 'up')).toEqual("CON") @@ -1242,9 +1242,9 @@ describe('Listeners', () => { describe('Exceptions', () => { test('node not on path', () => { const getLockForLink = (from: Lock, to: Lock) => { - return creator.makeLinkLock() + return creator.makeLinkLock(from.id, to.id) } - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id) const nodeA = creator.makeLock('nodeA') const nodeB = creator.makeLock('nodeB') const nodeC = creator.makeLock('nodeC') @@ -1252,7 +1252,7 @@ describe('Exceptions', () => { const makeLocker = creator.makeMakeLocker( node => node, - (from, to) => creator.makeLinkLock()) + (from, to) => creator.makeLinkLock(from.id, to.id)) const test1Path = [nodeA, nodeB, nodeC] const test1At = makeLocker("test1").makePathLocker(test1Path)( @@ -1264,14 +1264,14 @@ describe('Exceptions', () => { expect(nodeB.isLocked()).toBeFalsy() expect(nodeC.isLocked()).toBeFalsy() - expect(() => test1At.lockNext(nodeB)).not.toThrow() + expect(() => test1At.lockNext('nodeB')).not.toThrow() expect(() => test1At.arrivedAt(test1Path.indexOf(nodeB))).not.toThrow() expect(logSpyError).not.toHaveBeenCalled() expect(nodeA.isLocked()).toBeFalsy() expect(nodeB.isLocked()).toBeTruthy() expect(nodeC.isLocked()).toBeTruthy() - expect(() => test1At.lockNext(nodeX)).not.toThrow() + expect(() => test1At.lockNext('nodeX')).not.toThrow() expect(logSpyError).toHaveBeenCalled() // and all nodes are unlocked again expect(nodeA.isLocked()).toBeFalsy() @@ -1283,9 +1283,9 @@ describe('Exceptions', () => { }) test('arrivedAt bounds', () => { const getLockForLink = (from: Lock, to: Lock) => { - return creator.makeLinkLock() + return creator.makeLinkLock(from.id, to.id) } - const creator = new Graferse(node => node) + const creator = new Graferse(node => node?.id) const nodeA = creator.makeLock('nodeA') const nodeB = creator.makeLock('nodeB') const nodeC = creator.makeLock('nodeC') @@ -1293,10 +1293,10 @@ describe('Exceptions', () => { const makeLocker = creator.makeMakeLocker( node => node, - (from, to) => creator.makeLinkLock()) + (from, to) => creator.makeLinkLock(from.id, to.id)) const test1Path = [nodeA, nodeB, nodeC] - var forwardPath: Array> = [] + var forwardPath: Array> = [] const test1At = makeLocker("test1").makePathLocker(test1Path)( (nextNodes) => { forwardPath = nextNodes } ) @@ -1317,7 +1317,7 @@ describe('Exceptions', () => { expect(nodeA.isLocked()).toBeTruthy() expect(nodeB.isLocked()).toBeFalsy() expect(nodeC.isLocked()).toBeFalsy() - expect(forwardPath).toEqual([{index: 0, node: nodeA}]) + expect(forwardPath).toEqual([{index: 0, node: 'nodeA'}]) test1At.arrivedAt(3) // and all nodes are unlocked again diff --git a/src/graph.ts b/src/graph.ts index a787901..463b4a6 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -93,8 +93,8 @@ class LinkLock { throw new Error(`no such direction ${direction}`) } - constructor (to: string, from: string) { - this._allowed_directions = new Set([to, from]) + constructor (from: string, to: string) { + this._allowed_directions = new Set([from, to]) } getDetails() { @@ -185,10 +185,10 @@ class Graferse return lock } - makeLinkLock(isBidirectional: boolean = false) { + makeLinkLock(from: string, to: string, isBidirectional: boolean = false) { const linkLock = isBidirectional - ? new LinkLock("up", "down") - : new OnewayLinkLock("up", "down") + ? new LinkLock(from, to) + : new OnewayLinkLock(from, to) this.linkLocks.push(linkLock) return linkLock From a7f775fbb41ca07f6f43fdbd6eda1e0142bd00a5 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 09:26:58 +0200 Subject: [PATCH 17/18] concede to using strings as ids --- src/graph.test.ts | 64 +++++++++++++++++++++++------------------------ src/graph.ts | 15 +++++------ 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/graph.test.ts b/src/graph.test.ts index 58c729d..96bf01a 100644 --- a/src/graph.test.ts +++ b/src/graph.test.ts @@ -11,7 +11,7 @@ const getLockForLink = (from: Node, to: Node) => { describe('Graferse class', () => { test('creating locks', () => { - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) expect(creator.locks).toEqual([]) expect(creator.linkLocks).toEqual([]) @@ -26,7 +26,7 @@ describe('Graferse class', () => { expect(creator.linkLocks).toEqual([linkLock1]) }) test('lock groups', () => { - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) const lock1 = creator.makeLock('lock1') const lock2 = creator.makeLock('lock2') creator.setLockGroup([lock1, lock2]) @@ -43,7 +43,7 @@ describe('Graferse class', () => { expect(lock1.unlock("agent1")).toEqual(new Set(["agent2"])) }) test('clearAllLocks', () => { - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) const lock1 = creator.makeLock('lock1') const lock2 = creator.makeLock('lock2') const linkLock1 = creator.makeLinkLock('up', 'down', true) @@ -101,8 +101,8 @@ describe('no dependencies', () => { getLockForLink, ) - const forwardPaths1: Array>> = [] - const forwardPaths2: Array>> = [] + const forwardPaths1: Array> = [] + const forwardPaths2: Array> = [] const test1At = makeLocker("test1").makePathLocker(path1)( (nextNodes) => { forwardPaths1.push(nextNodes) } @@ -171,8 +171,8 @@ describe('no dependencies', () => { const makeLocker = creator.makeMakeLocker(node => node, getLockForLink) - let forwardPath1: Array> = [] - let forwardPath2: Array> = [] + let forwardPath1: Array = [] + let forwardPath2: Array = [] const test1At = makeLocker("test1").makePathLocker(path1)( (nextNodes) => { forwardPath1 = nextNodes } @@ -241,7 +241,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() - var forwardPath: Array> = [] + var forwardPath: Array = [] const makeLocker = creator.makeMakeLocker( node => node.data, getLockForLink)("agent1").makePathLocker @@ -296,7 +296,7 @@ describe('ngraph', () => { const path = pathFinder.find('a', 'c').reverse() const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink)("agent1").makePathLocker - var forwardPath: Array> = [] + var forwardPath: Array = [] const locker = makeLocker(path)((nextNodes) => { forwardPath = nextNodes }) const arrivedAt = (nodeId: string) => locker.arrivedAt(path.findIndex(node => node.id === nodeId)) @@ -344,7 +344,7 @@ describe('ngraph', () => { const pathFinder = ngraphPath.aStar(graph, { oriented: true }) const path = pathFinder.find('a', 'c').reverse() - var forwardPath: Array> = [] + var forwardPath: Array = [] const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink)("agent1").makePathLocker const locker = makeLocker(path)((nextNodes) => { forwardPath = nextNodes }) const arrivedAt = (nodeId: string) => @@ -394,8 +394,8 @@ describe('ngraph', () => { const s1Path = pathFinder.find('a', 'd').reverse() const s2Path = pathFinder.find('b', 'c').reverse() - var s1ForwardPath: Array> = [] - var s2ForwardPath: Array> = [] + var s1ForwardPath: Array = [] + var s2ForwardPath: Array = [] const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) const s2LockNext = makeLocker("agent2").makePathLocker(s2Path)((nextNodes) => { s2ForwardPath = nextNodes }) @@ -505,8 +505,8 @@ describe('ngraph', () => { node => node.data, getLockForLink, ) - const s1NextPaths: Array>> = [] - const s2NextPaths: Array>> = [] + const s1NextPaths: Array> = [] + const s2NextPaths: Array> = [] var s1calls = 0 var s2calls = 0 const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { @@ -688,7 +688,7 @@ describe('ngraph', () => { const s1Path = pathFinder.find('a', 'e').reverse() const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) - var s1ForwardPath: Array> = [] + var s1ForwardPath: Array = [] const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) // all nodes are unlocked @@ -729,7 +729,7 @@ describe('ngraph', () => { // an opposing robot appears const s2Path = pathFinder.find('d', 'b').reverse() - var s2ForwardPath: Array> = [] + var s2ForwardPath: Array = [] const s2LockNext = makeLocker("agent2").makePathLocker(s2Path)((nextNodes) => { s2ForwardPath = nextNodes }) s2LockNext.arrivedAt(s2Path.indexOf(nodeD)) @@ -803,7 +803,7 @@ describe('ngraph', () => { const s1Path = pathFinder.find('a', 'e').reverse() const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) - var s1ForwardPath: Array> = [] + var s1ForwardPath: Array = [] const s1LockNext = makeLocker("agent1").makePathLocker(s1Path)((nextNodes) => { s1ForwardPath = nextNodes }) expect(s1ForwardPath).toEqual([]) @@ -814,7 +814,7 @@ describe('ngraph', () => { // an opposing robot appears const s2Path = pathFinder.find('d', 'a').reverse() - var s2ForwardPath: Array> = [] + var s2ForwardPath: Array = [] const s2LockNext = makeLocker("agent2").makePathLocker(s2Path)((nextNodes) => { s2ForwardPath = nextNodes }) //console.log({s2Path}) s2LockNext.arrivedAt(s2Path.indexOf(nodeD)) @@ -827,7 +827,7 @@ describe('ngraph', () => { expect(s2ForwardPath).toEqual([{index: 0, node: 'd'}]) const s3Path = pathFinder.find('a', 'f').reverse() - var s3ForwardPath: Array> = [] + var s3ForwardPath: Array = [] const s3LockNext = makeLocker("agent3").makePathLocker(s3Path)((nextNodes) => { s3ForwardPath = nextNodes }) //console.warn('s3 stepping to node a') @@ -922,8 +922,8 @@ describe('ngraph', () => { const path2 = pathFinder.find('g', 'y').reverse() const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) - var nextNodes1: Array> = [] - var nextNodes2: Array> = [] + var nextNodes1: Array = [] + var nextNodes2: Array = [] const agent1at = makeLocker("agent1").makePathLocker(path1)((nn) => { nextNodes1 = nn }).lockNext const agent2at = makeLocker("agent2").makePathLocker(path2)((nn) => { nextNodes2 = nn }).lockNext @@ -1024,8 +1024,8 @@ describe('ngraph', () => { const path2 = pathFinder.find('g', 'y').reverse() const makeLocker = creator.makeMakeLocker(node => node.data, getLockForLink) - var nextNodes1: Array> = [] - var nextNodes2: Array> = [] + var nextNodes1: Array = [] + var nextNodes2: Array = [] const agent1at = makeLocker("agent1").makePathLocker(path1)((nn) => { nextNodes1 = nn }).lockNext const agent2at = makeLocker("agent2").makePathLocker(path2)((nn) => { nextNodes2 = nn }).lockNext @@ -1158,7 +1158,7 @@ describe('ngraph', () => { describe('Components', () => { describe('Lock', () => { test('locking twice', () => { - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) const lock = creator.makeLock('lock') expect(lock.requestLock('test', 'abc')).toBeTruthy() expect(lock.requestLock('test', 'def')).toBeTruthy() @@ -1169,7 +1169,7 @@ describe('Components', () => { test('locking when directed edge', () => { const logSpyWarn = jest.spyOn(console, 'warn').mockImplementation() const logSpyError = jest.spyOn(console, 'error').mockImplementation() - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) const linkLock = creator.makeLinkLock('up', 'down') // by default is directed edge expect(logSpyWarn).not.toHaveBeenCalled() expect(logSpyError).not.toHaveBeenCalled() @@ -1184,20 +1184,20 @@ describe('Components', () => { describe('Locking in both directions', () => { test('single owner can lock both directions', () => { - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent1', 'down')).toEqual("FREE") }) test('owner cannot lock both directions if multiple owners', () => { - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent2', 'up')).toEqual("PRO") expect(linkLock.requestLock('agent1', 'down')).toEqual("CON") }) test('agent cannot lock if both directions already locked', () => { - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent1', 'down')).toEqual("FREE") @@ -1217,7 +1217,7 @@ describe('Components', () => { describe('Listeners', () => { test('smoke', () => { let listenCallbackCounter = 0 - const creator = new Graferse(node => node) + const creator = new Graferse(node => node.id as string) creator.addListener(() => { listenCallbackCounter++ }) // no one has been called yet @@ -1244,7 +1244,7 @@ describe('Exceptions', () => { const getLockForLink = (from: Lock, to: Lock) => { return creator.makeLinkLock(from.id, to.id) } - const creator = new Graferse(node => node.id) + const creator = new Graferse(node => node.id) const nodeA = creator.makeLock('nodeA') const nodeB = creator.makeLock('nodeB') const nodeC = creator.makeLock('nodeC') @@ -1285,7 +1285,7 @@ describe('Exceptions', () => { const getLockForLink = (from: Lock, to: Lock) => { return creator.makeLinkLock(from.id, to.id) } - const creator = new Graferse(node => node?.id) + const creator = new Graferse(node => node?.id) const nodeA = creator.makeLock('nodeA') const nodeB = creator.makeLock('nodeB') const nodeC = creator.makeLock('nodeC') @@ -1296,7 +1296,7 @@ describe('Exceptions', () => { (from, to) => creator.makeLinkLock(from.id, to.id)) const test1Path = [nodeA, nodeB, nodeC] - var forwardPath: Array> = [] + var forwardPath: Array = [] const test1At = makeLocker("test1").makePathLocker(test1Path)( (nextNodes) => { forwardPath = nextNodes } ) diff --git a/src/graph.ts b/src/graph.ts index 463b4a6..6dc1e28 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -162,19 +162,20 @@ class OnewayLinkLock extends LinkLock { } } -type NextNode = { node: U, index: number } +type NextNode = { node: string, index: number } // TODO add a keep alive where owners need to report in periodically, else their locks will be freed -class Graferse +// where T is the type you will supply the path in +class Graferse { locks: Lock[] = [] linkLocks: LinkLock[] = [] lockGroups: Lock[][] = [] lastCallCache = new Map void>() listeners: Array<() => void> = [] - identity: (x: T) => U + identity: (x: T) => string constructor( - identity: (x: T) => U, // returns external node identity + identity: (x: T) => string, // returns external node identity ) { this.identity = identity } @@ -252,7 +253,7 @@ class Graferse getLock: (x: T) => Lock, // given a T, gives you a Lock getLockForLink: (from: T, to: T) => LinkLock, ) { - type NextNodes = (nextNodes: NextNode[], remaining: number) => void + type NextNodes = (nextNodes: NextNode[], remaining: number) => void return (byWhom: string) => { const makePathLocker = (path: T[]) => (callback: NextNodes) => { // given an index in the path, tries to lock all bidirectional edges @@ -302,7 +303,7 @@ class Graferse this.notifyWaiters(whoCanMoveNow) } - const lockNext = (currentNode: U) => { + const lockNext = (currentNode: string) => { console.warn("lockNext is deprecated, please use arrivedAt") const currentIdx = path.findIndex(node => this.identity(node) === currentNode) if (currentIdx === -1) { @@ -325,7 +326,7 @@ class Graferse const lastToLock = Math.min(currentIdx + afterCount, lastIdx) // last to be locked const whoCanMoveNow = new Set() - const nextNodes: NextNode[] = [] + const nextNodes: NextNode[] = [] // go through path from start to last node to be locked for (let i = 0; i <= lastToLock; i++) { // unlock all edges before current position From aeeafadca6abcc14baa849b22a04fba0bc892742 Mon Sep 17 00:00:00 2001 From: Aaron Lipinski Date: Thu, 21 Sep 2023 09:31:28 +0200 Subject: [PATCH 18/18] permit numbers as identifiers --- src/graph.test.ts | 38 +++++++++++++++++++------------------- src/graph.ts | 7 ++++--- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/graph.test.ts b/src/graph.test.ts index 96bf01a..32ce61a 100644 --- a/src/graph.test.ts +++ b/src/graph.test.ts @@ -11,7 +11,7 @@ const getLockForLink = (from: Node, to: Node) => { describe('Graferse class', () => { test('creating locks', () => { - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) expect(creator.locks).toEqual([]) expect(creator.linkLocks).toEqual([]) @@ -26,7 +26,7 @@ describe('Graferse class', () => { expect(creator.linkLocks).toEqual([linkLock1]) }) test('lock groups', () => { - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) const lock1 = creator.makeLock('lock1') const lock2 = creator.makeLock('lock2') creator.setLockGroup([lock1, lock2]) @@ -43,7 +43,7 @@ describe('Graferse class', () => { expect(lock1.unlock("agent1")).toEqual(new Set(["agent2"])) }) test('clearAllLocks', () => { - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) const lock1 = creator.makeLock('lock1') const lock2 = creator.makeLock('lock2') const linkLock1 = creator.makeLinkLock('up', 'down', true) @@ -228,7 +228,7 @@ describe('no dependencies', () => { describe('ngraph', () => { test('basic locking', () => { const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -282,7 +282,7 @@ describe('ngraph', () => { test('basic locking - clearAllPathLocks', () => { const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -331,7 +331,7 @@ describe('ngraph', () => { test('unexpected queue jumping', () => { const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -371,7 +371,7 @@ describe('ngraph', () => { test('two robot mutual exclusion', () => { const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -450,7 +450,7 @@ describe('ngraph', () => { test('bidirectional corridor convoy', () => { const graph = ngraphCreateGraph() - const creator = new Graferse>(x => x.id as string) + const creator = new Graferse>(x => x.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -656,7 +656,7 @@ describe('ngraph', () => { test('bidirectional corridor with early exit', () => { const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -769,7 +769,7 @@ describe('ngraph', () => { test('three agents bidirectional corridor with early exit', () => { const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -873,7 +873,7 @@ describe('ngraph', () => { // v // Z const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -974,7 +974,7 @@ describe('ngraph', () => { // v // Z const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -1132,7 +1132,7 @@ describe('ngraph', () => { //}) test('directed', () => { const graph = ngraphCreateGraph() - const creator = new Graferse>(node => node.id as string) + const creator = new Graferse>(node => node.id) const makeNode = (id: string) => graph.addNode(id, creator.makeLock(id)) const nodeA = makeNode('a') @@ -1158,7 +1158,7 @@ describe('ngraph', () => { describe('Components', () => { describe('Lock', () => { test('locking twice', () => { - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) const lock = creator.makeLock('lock') expect(lock.requestLock('test', 'abc')).toBeTruthy() expect(lock.requestLock('test', 'def')).toBeTruthy() @@ -1169,7 +1169,7 @@ describe('Components', () => { test('locking when directed edge', () => { const logSpyWarn = jest.spyOn(console, 'warn').mockImplementation() const logSpyError = jest.spyOn(console, 'error').mockImplementation() - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) const linkLock = creator.makeLinkLock('up', 'down') // by default is directed edge expect(logSpyWarn).not.toHaveBeenCalled() expect(logSpyError).not.toHaveBeenCalled() @@ -1184,20 +1184,20 @@ describe('Components', () => { describe('Locking in both directions', () => { test('single owner can lock both directions', () => { - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent1', 'down')).toEqual("FREE") }) test('owner cannot lock both directions if multiple owners', () => { - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent2', 'up')).toEqual("PRO") expect(linkLock.requestLock('agent1', 'down')).toEqual("CON") }) test('agent cannot lock if both directions already locked', () => { - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) const linkLock = creator.makeLinkLock('up', 'down', true) // is bidirectional expect(linkLock.requestLock('agent1', 'up')).toEqual("FREE") expect(linkLock.requestLock('agent1', 'down')).toEqual("FREE") @@ -1217,7 +1217,7 @@ describe('Components', () => { describe('Listeners', () => { test('smoke', () => { let listenCallbackCounter = 0 - const creator = new Graferse(node => node.id as string) + const creator = new Graferse(node => node.id) creator.addListener(() => { listenCallbackCounter++ }) // no one has been called yet diff --git a/src/graph.ts b/src/graph.ts index 6dc1e28..7ccba8d 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -162,7 +162,8 @@ class OnewayLinkLock extends LinkLock { } } -type NextNode = { node: string, index: number } +type id = string | number +type NextNode = { node: id, index: number } // TODO add a keep alive where owners need to report in periodically, else their locks will be freed // where T is the type you will supply the path in class Graferse @@ -172,10 +173,10 @@ class Graferse lockGroups: Lock[][] = [] lastCallCache = new Map void>() listeners: Array<() => void> = [] - identity: (x: T) => string + identity: (x: T) => id constructor( - identity: (x: T) => string, // returns external node identity + identity: (x: T) => id, // returns external node identity ) { this.identity = identity }