mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Correct handling for multiple loops
This commit is contained in:
@@ -192,18 +192,28 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
}
|
||||
|
||||
const newEdges = [...get().edges, newEdge]
|
||||
const { hasCycle, path } = detectCycle(newEdges, edge.source)
|
||||
|
||||
// Only create a loop if we have a valid cycle with at least 2 unique nodes
|
||||
const newLoops = { ...get().loops }
|
||||
// Recalculate all loops after adding the edge
|
||||
const newLoops: Record<string, Loop> = {}
|
||||
const processedPaths = new Set<string>()
|
||||
|
||||
if (hasCycle && path.length > 1) {
|
||||
const loopId = crypto.randomUUID()
|
||||
newLoops[loopId] = {
|
||||
id: loopId,
|
||||
nodes: path
|
||||
}
|
||||
}
|
||||
// Check for cycles from each node
|
||||
const nodes = new Set(newEdges.map(e => e.source))
|
||||
nodes.forEach(node => {
|
||||
const { paths } = detectCycle(newEdges, node)
|
||||
paths.forEach(path => {
|
||||
// Create a canonical path representation for deduplication
|
||||
const canonicalPath = [...path].sort().join(',')
|
||||
if (!processedPaths.has(canonicalPath)) {
|
||||
const loopId = crypto.randomUUID()
|
||||
newLoops[loopId] = {
|
||||
id: loopId,
|
||||
nodes: path
|
||||
}
|
||||
processedPaths.add(canonicalPath)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const newState = {
|
||||
blocks: { ...get().blocks },
|
||||
@@ -219,23 +229,26 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
removeEdge: (edgeId: string) => {
|
||||
const newEdges = get().edges.filter((edge) => edge.id !== edgeId)
|
||||
|
||||
// Recalculate loops after edge removal
|
||||
// Recalculate all loops after edge removal
|
||||
const newLoops: Record<string, Loop> = {}
|
||||
const processedNodes = new Set<string>()
|
||||
const processedPaths = new Set<string>()
|
||||
|
||||
// Check for cycles from each source node
|
||||
newEdges.forEach(edge => {
|
||||
if (!processedNodes.has(edge.source)) {
|
||||
const { hasCycle, path } = detectCycle(newEdges, edge.source)
|
||||
if (hasCycle) {
|
||||
// Check for cycles from each node
|
||||
const nodes = new Set(newEdges.map(e => e.source))
|
||||
nodes.forEach(node => {
|
||||
const { paths } = detectCycle(newEdges, node)
|
||||
paths.forEach(path => {
|
||||
// Create a canonical path representation for deduplication
|
||||
const canonicalPath = [...path].sort().join(',')
|
||||
if (!processedPaths.has(canonicalPath)) {
|
||||
const loopId = crypto.randomUUID()
|
||||
newLoops[loopId] = {
|
||||
id: loopId,
|
||||
nodes: path
|
||||
}
|
||||
processedPaths.add(canonicalPath)
|
||||
}
|
||||
processedNodes.add(edge.source)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const newState = {
|
||||
|
||||
@@ -1,68 +1,53 @@
|
||||
import { Edge } from 'reactflow'
|
||||
|
||||
/**
|
||||
* Performs a depth-first search to detect cycles in the graph
|
||||
* Performs a depth-first search to detect all cycles in the graph
|
||||
* @param edges - List of all edges in the graph
|
||||
* @param startNode - Starting node for cycle detection
|
||||
* @returns boolean indicating if a cycle was detected and the path of the cycle if found
|
||||
* @returns Array of all unique cycles found in the graph
|
||||
*/
|
||||
export function detectCycle(edges: Edge[], startNode: string): { hasCycle: boolean; path: string[] } {
|
||||
export function detectCycle(edges: Edge[], startNode: string): { hasCycle: boolean; paths: string[][] } {
|
||||
const visited = new Set<string>()
|
||||
const recursionStack = new Set<string>()
|
||||
const pathMap = new Map<string, string>()
|
||||
|
||||
function dfs(node: string): boolean {
|
||||
// Add to both visited and recursion stack
|
||||
const allCycles: string[][] = []
|
||||
const currentPath: string[] = []
|
||||
|
||||
function dfs(node: string) {
|
||||
visited.add(node)
|
||||
recursionStack.add(node)
|
||||
|
||||
currentPath.push(node)
|
||||
|
||||
// Get all neighbors of current node
|
||||
const neighbors = edges
|
||||
.filter(edge => edge.source === node)
|
||||
.map(edge => edge.target)
|
||||
|
||||
|
||||
for (const neighbor of neighbors) {
|
||||
// If not visited, explore that path
|
||||
if (!visited.has(neighbor)) {
|
||||
pathMap.set(neighbor, node)
|
||||
if (dfs(neighbor)) return true
|
||||
}
|
||||
// If the neighbor is in recursion stack, we found a cycle
|
||||
else if (recursionStack.has(neighbor)) {
|
||||
// Record the last edge of the cycle
|
||||
pathMap.set(neighbor, node)
|
||||
return true
|
||||
if (!recursionStack.has(neighbor)) {
|
||||
if (!visited.has(neighbor)) {
|
||||
dfs(neighbor)
|
||||
}
|
||||
} else {
|
||||
// Found a cycle
|
||||
const cycleStartIndex = currentPath.indexOf(neighbor)
|
||||
if (cycleStartIndex !== -1) {
|
||||
const cycle = currentPath.slice(cycleStartIndex)
|
||||
// Only add cycles with length > 1
|
||||
if (cycle.length > 1) {
|
||||
allCycles.push([...cycle])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from recursion stack when backtracking
|
||||
|
||||
currentPath.pop()
|
||||
recursionStack.delete(node)
|
||||
return false
|
||||
}
|
||||
|
||||
// Perform DFS and construct cycle path if found
|
||||
const hasCycle = dfs(startNode)
|
||||
|
||||
// If cycle found, construct the path and ensure no duplicates
|
||||
const cyclePath: string[] = []
|
||||
if (hasCycle) {
|
||||
let current = startNode
|
||||
const seenNodes = new Set<string>()
|
||||
|
||||
do {
|
||||
// Only add node if we haven't seen it before
|
||||
if (!seenNodes.has(current)) {
|
||||
cyclePath.unshift(current)
|
||||
seenNodes.add(current)
|
||||
}
|
||||
current = pathMap.get(current)!
|
||||
} while (current !== startNode && current !== undefined)
|
||||
|
||||
// Add starting node only if it's not already in the path
|
||||
if (current === startNode && !seenNodes.has(startNode)) {
|
||||
cyclePath.unshift(startNode)
|
||||
}
|
||||
|
||||
dfs(startNode)
|
||||
|
||||
return {
|
||||
hasCycle: allCycles.length > 0,
|
||||
paths: allCycles
|
||||
}
|
||||
|
||||
return { hasCycle, path: cyclePath }
|
||||
}
|
||||
Reference in New Issue
Block a user