diff --git a/src/js/ibcJS.js b/src/js/ibcJS.js index 7ac1750f6a171a038573048d41b27ae5ff98f75a..2832cfb34ede5db8285cf395aad6d11f499cd160 100644 --- a/src/js/ibcJS.js +++ b/src/js/ibcJS.js @@ -196,6 +196,31 @@ globalThis.ibc = (() => { slaves.forEach(s => create_node_rec(s)); + let detectCycle = function(node, visited, recStack) { // doesn't have to be fast, will only be called after the stack explodes + if (!visited[node.id]) { + visited[node.id] = true; + recStack.push(node.id); + if (node.father) { + if (!visited[node.father.id] && detectCycle(node.father, visited, recStack)) { + return true; + } else if (recStack.includes(node.father.id)) { + recStack.push(node.father.id); + return true; + } + } + if (node.mother) { + if (!visited[node.mother.id] && detectCycle(node.mother, visited, recStack)) { + return true; + } else if (recStack.includes(node.mother.id)) { + recStack.push(node.mother.id); + return true; + } + } + } + recStack.pop(); + return false; + } + let countSelfAndAncestors = function(node) { if (node === null) return 0 + 0; // no self, no ancestors @@ -205,8 +230,27 @@ globalThis.ibc = (() => { return 1 + node._ancestorNodeCount; // 1 self, maybe some ancestors }; - for (let slave of slaves) - countSelfAndAncestors(nodes[slave.ID]); + for (let slave of slaves) { + try { + countSelfAndAncestors(nodes[slave.ID]); + } catch (e) { + // probably a cycle, try to go find it and throw a better error + const visited = {}; + const recStack = []; // track back edges in node graph + const IDToName = (id) => { + try { + return SlaveFullName(world.findSlaveState(id)); + } catch { + return ``; + } + } + if (detectCycle(nodes[slave.ID], visited, recStack)) { + throw new Error(`Heritance cycle detected: ${toSentence(recStack.map(s => `${s} (${IDToName(s)})`), " was born to ", " was born to ")}, making them their own ancestor...`); + } else { + throw e; // not a cycle? no idea then + } + } + } return nodes; };