From ff5de28d4927d3cea4e1dd64554ee84e58ae7f55 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 7 May 2025 10:36:42 +0530 Subject: [PATCH] Refactor simulation components for improved functionality and performance - Update SimulationPlayer to enhance reset handling and progress display - Modify useDelayHandler to incorporate animation speed and improve delay management - Revamp useSpawnHandler to utilize state for active spawns and optimize spawn logic - Integrate reset functionality in Products and Simulator components - Implement execution order and sequence determination in new utility functions - Add trigger extraction logic for better event handling --- .../ui/simulation/simulationPlayer.tsx | 9 +- .../conveyor/actionHandler/useDelayHandler.ts | 54 +++-- .../conveyor/actionHandler/useSpawnHandler.ts | 185 +++++++------- .../modules/simulation/products/products.tsx | 8 +- .../functions/determineExecutionOrder.ts | 114 +++++++++ .../functions/determineExecutionSequences.ts | 101 ++++++++ .../functions/extractTriggersFromPoint.ts | 9 + .../simulation/simulator/simulator.tsx | 229 +----------------- 8 files changed, 374 insertions(+), 335 deletions(-) create mode 100644 app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts create mode 100644 app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts create mode 100644 app/src/modules/simulation/simulator/functions/extractTriggersFromPoint.ts diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index e53e1c8..360d166 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -40,9 +40,9 @@ const SimulationPlayer: React.FC = () => { useEffect(() => { if (isReset) { - setTimeout(()=>{ + setTimeout(() => { setReset(false); - },0) + }, 0) } }, [isReset]) @@ -282,11 +282,10 @@ const SimulationPlayer: React.FC = () => { {index < intervals.length - 1 && (
= ((index + 1) / totalSegments) * 100 + className={`line ${progress >= ((index + 1) / totalSegments) * 100 ? "filled" : "" - }`} + }`} >
)} diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/useDelayHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/useDelayHandler.ts index f49a816..6aeb2ef 100644 --- a/app/src/modules/simulation/actions/conveyor/actionHandler/useDelayHandler.ts +++ b/app/src/modules/simulation/actions/conveyor/actionHandler/useDelayHandler.ts @@ -1,22 +1,27 @@ import { useCallback, useEffect, useRef } from "react"; import { useFrame } from "@react-three/fiber"; -import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore } from "../../../../../store/usePlayButtonStore"; +import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore"; import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore"; interface DelayInstance { + initialDelay: number; delayEndTime: number; materialId?: string; action: ConveyorAction; isPaused: boolean; remainingTime: number; + lastUpdateTime: number; + elapsedTime: number; } export function useDelayHandler() { const { isPlaying } = usePlayButtonStore(); const { isPaused } = usePauseButtonStore(); const { isReset } = useResetButtonStore(); + const { speed } = useAnimationPlaySpeed(); const { setIsPaused } = useMaterialStore(); const activeDelays = useRef>(new Map()); + const lastUpdateTimeRef = useRef(performance.now()); const cleanupDelay = useCallback(() => { activeDelays.current.clear(); @@ -30,34 +35,33 @@ export function useDelayHandler() { const delayLogStatus = (materialUuid: string, status: string) => { // console.log(`${materialUuid}, ${status}`); - } + }; - useEffect(() => { + useFrame(() => { const currentTime = performance.now(); + const deltaTime = currentTime - lastUpdateTimeRef.current; + lastUpdateTimeRef.current = currentTime; + const completedDelays: string[] = []; - activeDelays.current.forEach((delay) => { + activeDelays.current.forEach((delay, key) => { if (isPaused && !delay.isPaused) { delay.remainingTime = Math.max(0, delay.delayEndTime - currentTime); delay.isPaused = true; } else if (!isPaused && delay.isPaused) { delay.delayEndTime = currentTime + delay.remainingTime; delay.isPaused = false; - delay.remainingTime = 0; - } - }); - }, [isPaused]); + delay.lastUpdateTime = currentTime; + delay.elapsedTime = 0; + } else if (!delay.isPaused && isPlaying) { + delay.elapsedTime += deltaTime * speed; - useFrame(() => { - if (!isPlaying || isPaused) return; - - const currentTime = performance.now(); - const completedDelays: string[] = []; - - activeDelays.current.forEach((delay, key) => { - if (!delay.isPaused && currentTime >= delay.delayEndTime && delay.materialId) { - delayLogStatus(delay.materialId, `Delay completed}`); - setIsPaused(delay.materialId, false); - completedDelays.push(key); + if (delay.elapsedTime >= delay.initialDelay) { + if (delay.materialId) { + delayLogStatus(delay.materialId, `Delay completed`); + setIsPaused(delay.materialId, false); + } + completedDelays.push(key); + } } }); @@ -74,22 +78,24 @@ export function useDelayHandler() { const key = materialId ? `${materialId}-${action.actionUuid}` : action.actionUuid; - // If this material already has a delay, cancel it if (activeDelays.current.has(key)) { activeDelays.current.delete(key); } + const now = performance.now(); activeDelays.current.set(key, { - delayEndTime: performance.now() + delayMs, + initialDelay: delayMs, + delayEndTime: now + delayMs, materialId, action, isPaused: false, - remainingTime: 0 + remainingTime: 0, + lastUpdateTime: now, + elapsedTime: 0 }); - delayLogStatus(materialId, `Started ${delayMs * 1000}s delay`); + delayLogStatus(materialId, `Started ${delayMs / 1000}s delay`); setIsPaused(materialId, true); - }, [isPlaying]); useEffect(() => { diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts index e12e538..bc80d8a 100644 --- a/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts +++ b/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useState } from "react"; import * as THREE from 'three'; import { useFrame } from "@react-three/fiber"; import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore"; @@ -32,10 +32,23 @@ export function useSpawnHandler() { const { isReset } = useResetButtonStore(); const { selectedProduct } = useSelectedProduct(); - const activeSpawns = useRef>(new Map()); + const [activeSpawns, setActiveSpawns] = useState>(new Map()); + + const getConveyorPausedState = useCallback((action: ConveyorAction) => { + const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid); + if (!modelUuid) return false; + + const conveyor = getConveyorById(modelUuid); + if (!conveyor) return false; + return conveyor.isPaused; + }, [getConveyorById, getModelUuidByActionUuid, selectedProduct.productId]); + + const shouldPauseSpawn = useCallback((action: ConveyorAction) => { + return isPaused || getConveyorPausedState(action); + }, [isPaused, getConveyorPausedState]); const clearAllSpawns = useCallback(() => { - activeSpawns.current.clear(); + setActiveSpawns(new Map()); }, []); useEffect(() => { @@ -84,92 +97,96 @@ export function useSpawnHandler() { return newMaterial; }, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productId]); - - const getConveyorPausedState = useCallback((action: ConveyorAction) => { - const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid); - if (!modelUuid) return false; - - const conveyor = getConveyorById(modelUuid); - return conveyor?.isPaused ?? false; - }, [getConveyorById, getModelUuidByActionUuid, selectedProduct.productId]); - - useEffect(() => { + useFrame(() => { const currentTime = performance.now(); + const completedActions: string[] = []; + let hasChanges = false; - activeSpawns.current.forEach((spawn) => { - if (isPaused && !spawn.isPaused) { + activeSpawns.forEach(spawn => { + const isPausedNow = shouldPauseSpawn(spawn.params.action); + + if (isPausedNow && !spawn.isPaused) { if (spawn.lastSpawnTime === null) { - spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.startTime)); + spawn.remainingTime = Math.max(0, (spawn.params.intervalMs / speed) - (currentTime - spawn.startTime)); } else { - spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.lastSpawnTime)); + spawn.remainingTime = Math.max(0, (spawn.params.intervalMs / speed) - (currentTime - spawn.lastSpawnTime)); } spawn.pauseStartTime = currentTime; spawn.isPaused = true; - } else if (!isPaused && spawn.isPaused) { - if (spawn.remainingTime > 0) { - if (spawn.lastSpawnTime === null) { - spawn.startTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime); - } else { - spawn.lastSpawnTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime); - } + hasChanges = true; + } else if (!isPausedNow && spawn.isPaused) { + const pauseDuration = currentTime - spawn.pauseStartTime; + if (spawn.lastSpawnTime === null) { + spawn.startTime += pauseDuration; + } else { + spawn.lastSpawnTime += pauseDuration; } spawn.isPaused = false; spawn.pauseStartTime = 0; spawn.remainingTime = 0; + hasChanges = true; } }); - }, [isPaused]); - useFrame(() => { - if (!isPlaying || isPaused || isReset) return; + if (isPlaying && !isReset && !isPaused) { + activeSpawns.forEach((spawn, actionUuid) => { + if (spawn.isPaused) return; - const currentTime = performance.now(); - const completedActions: string[] = []; + const { material, intervalMs, totalCount, action } = spawn.params; + const adjustedInterval = intervalMs / speed; + const isFirstSpawn = spawn.lastSpawnTime === null; - activeSpawns.current.forEach((spawn, actionUuid) => { - const { material, intervalMs, totalCount, action } = spawn.params; - const isFirstSpawn = spawn.lastSpawnTime === null; + // First spawn + if (isFirstSpawn) { + const elapsed = currentTime - spawn.startTime; + if (elapsed >= adjustedInterval) { + const createdMaterial = createNewMaterial(material, action); + if (createdMaterial) { + spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`); + } + spawn.lastSpawnTime = currentTime; + spawn.spawnCount = 1; + hasChanges = true; - // First spawn - if (isFirstSpawn) { - const elapsed = currentTime - spawn.startTime; - if (elapsed >= intervalMs) { - const createdMaterial = createNewMaterial(material, action); - if (createdMaterial) { - spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`); + if (totalCount <= 1) { + completedActions.push(actionUuid); + } } - spawn.lastSpawnTime = currentTime; - spawn.spawnCount = 1; + return; + } - if (totalCount <= 1) { - completedActions.push(actionUuid); + // Subsequent spawns + if (spawn.lastSpawnTime !== null) { + const timeSinceLast = currentTime - spawn.lastSpawnTime; + if (timeSinceLast >= adjustedInterval) { + const count = spawn.spawnCount + 1; + const createdMaterial = createNewMaterial(material, action); + if (createdMaterial) { + spawnLogStatus(createdMaterial.materialId, `[${timeSinceLast.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`); + } + spawn.lastSpawnTime = currentTime; + spawn.spawnCount = count; + hasChanges = true; + + if (count >= totalCount) { + completedActions.push(actionUuid); + } } } - return; - } + }); + } - // Subsequent spawns - if (spawn.lastSpawnTime !== null) { - const timeSinceLast = currentTime - spawn.lastSpawnTime; - if (timeSinceLast >= intervalMs) { - const count = spawn.spawnCount + 1; - const createdMaterial = createNewMaterial(material, action); - if (createdMaterial) { - spawnLogStatus(createdMaterial.materialId, `[${timeSinceLast.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`); - } - spawn.lastSpawnTime = currentTime; - spawn.spawnCount = count; + if (hasChanges || completedActions.length > 0) { + setActiveSpawns(prevSpawns => { + const newSpawns = new Map(prevSpawns); - if (count >= totalCount) { - completedActions.push(actionUuid); - } - } - } - }); + completedActions.forEach(actionUuid => { + newSpawns.delete(actionUuid); + }); - completedActions.forEach(actionUuid => { - activeSpawns.current.delete(actionUuid); - }); + return newSpawns; + }); + } }); const handleSpawn = useCallback((action: ConveyorAction) => { @@ -178,23 +195,29 @@ export function useSpawnHandler() { const { material, spawnInterval = 0, spawnCount = 1, actionUuid } = action; const intervalMs = spawnInterval * 1000; - if (activeSpawns.current.has(actionUuid)) { - activeSpawns.current.delete(actionUuid); - } + setActiveSpawns(prevSpawns => { + const newSpawns = new Map(prevSpawns); - activeSpawns.current.set(actionUuid, { - lastSpawnTime: null, - startTime: performance.now(), - spawnCount: 0, - params: { - material, - intervalMs, - totalCount: spawnCount, - action: action - }, - pauseStartTime: 0, - remainingTime: 0, - isPaused: false + if (newSpawns.has(actionUuid)) { + newSpawns.delete(actionUuid); + } + + newSpawns.set(actionUuid, { + lastSpawnTime: null, + startTime: performance.now(), + spawnCount: 0, + params: { + material, + intervalMs, + totalCount: spawnCount, + action: action + }, + pauseStartTime: 0, + remainingTime: 0, + isPaused: false + }); + + return newSpawns; }); }, []); diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index e1e8fa6..4a4807d 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -8,6 +8,7 @@ import { getAllProductsApi } from '../../../services/simulation/getallProductsAp import { useVehicleStore } from '../../../store/simulation/useVehicleStore'; import { useArmBotStore } from '../../../store/simulation/useArmBotStore'; import { useConveyorStore } from '../../../store/simulation/useConveyorStore'; +import { useResetButtonStore } from '../../../store/usePlayButtonStore'; function Products() { const { products, getProductById, addProduct, setProducts } = useProductStore(); @@ -15,6 +16,7 @@ function Products() { const { addVehicle, clearvehicles } = useVehicleStore(); const { addArmBot, clearArmBots } = useArmBotStore(); const { addConveyor, clearConveyors } = useConveyorStore(); + const { isReset } = useResetButtonStore(); useEffect(() => { const email = localStorage.getItem('email') @@ -45,7 +47,7 @@ function Products() { }); } } - }, [selectedProduct, products]); + }, [selectedProduct, products, isReset]); useEffect(() => { if (selectedProduct.productId) { @@ -59,7 +61,7 @@ function Products() { }); } } - }, [selectedProduct, products]); + }, [selectedProduct, products, isReset]); useEffect(() => { if (selectedProduct.productId) { @@ -73,7 +75,7 @@ function Products() { }); } } - }, [selectedProduct, products]); + }, [selectedProduct, products, isReset]); return ( <> diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts new file mode 100644 index 0000000..fd42547 --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts @@ -0,0 +1,114 @@ +import { extractTriggersFromPoint } from "./extractTriggersFromPoint"; + +export function determineExecutionOrder(products: productsSchema): PointsScheme[] { + // Create maps for all events and points + const eventMap = new Map(); + const pointMap = new Map(); + const allPoints: PointsScheme[] = []; + + // First pass: collect all points + products.forEach(product => { + product.eventDatas.forEach(event => { + eventMap.set(event.modelUuid, event); + + if (event.type === 'transfer') { + event.points.forEach(point => { + pointMap.set(point.uuid, point); + allPoints.push(point); + }); + } else if (event.type === 'vehicle' || + event.type === 'machine' || + event.type === 'storageUnit') { + pointMap.set(event.point.uuid, event.point); + allPoints.push(event.point); + } else if (event.type === 'roboticArm') { + pointMap.set(event.point.uuid, event.point); + allPoints.push(event.point); + } + }); + }); + + // Build dependency graphs + const graph = new Map(); + const reverseGraph = new Map(); + const allTriggeredPoints = new Set(); + + allPoints.forEach(point => { + const triggers = extractTriggersFromPoint(point); + const dependencies: string[] = []; + + triggers.forEach(trigger => { + const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid; + if (targetUuid && pointMap.has(targetUuid)) { + dependencies.push(targetUuid); + allTriggeredPoints.add(targetUuid); + + if (!reverseGraph.has(targetUuid)) { + reverseGraph.set(targetUuid, []); + } + reverseGraph.get(targetUuid)!.push(point.uuid); + } + }); + + graph.set(point.uuid, dependencies); + }); + + // Identify root points (points that trigger others but aren't triggered themselves) + const rootPoints = allPoints + .filter(point => !allTriggeredPoints.has(point.uuid)) + .filter(point => { + // Only include roots that actually have triggers pointing FROM them + const triggers = extractTriggersFromPoint(point); + return triggers.some(t => t.triggeredAsset?.triggeredPoint?.pointUuid); + }); + + // If no root points found but we have triggered points, find the earliest triggers + if (rootPoints.length === 0 && allTriggeredPoints.size > 0) { + // This handles cases where we have circular dependencies + // but still want to include the triggered points + const minTriggerCount = Math.min( + ...Array.from(allTriggeredPoints) + .map(uuid => (graph.get(uuid) || []).length) + ); + const potentialRoots = Array.from(allTriggeredPoints) + .filter(uuid => (graph.get(uuid) || []).length === minTriggerCount); + + rootPoints.push(...potentialRoots.map(uuid => pointMap.get(uuid)!)); + } + + // Topological sort only for triggered points + const visited = new Set(); + const temp = new Set(); + const order: string[] = []; + let hasCycle = false; + + function visit(node: string) { + if (temp.has(node)) { + hasCycle = true; + return; + } + if (visited.has(node)) return; + + temp.add(node); + + const dependencies = reverseGraph.get(node) || []; + for (const dep of dependencies) { + visit(dep); + } + + temp.delete(node); + visited.add(node); + order.push(node); + } + + // Start processing from root points + rootPoints.forEach(root => visit(root.uuid)); + + // Convert UUIDs back to points and filter out untriggered points + const triggeredPoints = order + .map(uuid => pointMap.get(uuid)!) + .filter(point => allTriggeredPoints.has(point.uuid) || + rootPoints.some(root => root.uuid === point.uuid)); + + return triggeredPoints; +} \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts new file mode 100644 index 0000000..afd5324 --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts @@ -0,0 +1,101 @@ +import { extractTriggersFromPoint } from "./extractTriggersFromPoint"; + +export function determineExecutionSequences(products: productsSchema): PointsScheme[][] { + // Create maps for all points + const pointMap = new Map(); + const allPoints: PointsScheme[] = []; + + // First pass: collect all points + products.forEach(product => { + product.eventDatas.forEach(event => { + if (event.type === 'transfer') { + event.points.forEach(point => { + pointMap.set(point.uuid, point); + allPoints.push(point); + }); + } else if (event.type === 'vehicle' || + event.type === 'machine' || + event.type === 'storageUnit' || + event.type === 'roboticArm') { + pointMap.set(event.point.uuid, event.point); + allPoints.push(event.point); + } + }); + }); + + // Build complete dependency graph + const dependencyGraph = new Map(); + const reverseDependencyGraph = new Map(); + const triggeredPoints = new Set(); + + allPoints.forEach(point => { + const triggers = extractTriggersFromPoint(point); + const dependencies: string[] = []; + + triggers.forEach(trigger => { + const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid; + if (targetUuid && pointMap.has(targetUuid)) { + dependencies.push(targetUuid); + triggeredPoints.add(targetUuid); + + if (!reverseDependencyGraph.has(targetUuid)) { + reverseDependencyGraph.set(targetUuid, []); + } + reverseDependencyGraph.get(targetUuid)!.push(point.uuid); + } + }); + + dependencyGraph.set(point.uuid, dependencies); + }); + + // Identify independent root points (points that trigger others but aren't triggered themselves) + const rootPoints = allPoints.filter(point => { + const hasOutgoingTriggers = extractTriggersFromPoint(point).some( + t => t.triggeredAsset?.triggeredPoint?.pointUuid + ); + return hasOutgoingTriggers && !triggeredPoints.has(point.uuid); + }); + + // For each root point, build its complete trigger chain + const executionSequences: PointsScheme[][] = []; + + function buildSequence(startUuid: string): PointsScheme[] { + const sequence: PointsScheme[] = []; + const visited = new Set(); + + function traverse(uuid: string) { + if (visited.has(uuid)) return; + visited.add(uuid); + + const point = pointMap.get(uuid); + if (point) { + sequence.push(point); + } + + // Follow forward dependencies + const nextPoints = dependencyGraph.get(uuid) || []; + nextPoints.forEach(nextUuid => traverse(nextUuid)); + } + + traverse(startUuid); + return sequence; + } + + // Build sequences for all root points + rootPoints.forEach(root => { + executionSequences.push(buildSequence(root.uuid)); + }); + + // Handle any triggered points not reachable from roots (isolated chains) + const processedPoints = new Set( + executionSequences.flat().map(p => p.uuid) + ); + + allPoints.forEach(point => { + if (triggeredPoints.has(point.uuid) && !processedPoints.has(point.uuid)) { + executionSequences.push(buildSequence(point.uuid)); + } + }); + + return executionSequences; +} \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/functions/extractTriggersFromPoint.ts b/app/src/modules/simulation/simulator/functions/extractTriggersFromPoint.ts new file mode 100644 index 0000000..012bc6b --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/extractTriggersFromPoint.ts @@ -0,0 +1,9 @@ + +export function extractTriggersFromPoint(point: PointsScheme): TriggerSchema[] { + if ('actions' in point) { + return point.actions.flatMap(action => action.triggers); + } else if ('action' in point) { + return point.action.triggers; + } + return []; +} \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx index c271286..8c9d8f0 100644 --- a/app/src/modules/simulation/simulator/simulator.tsx +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react'; import { useProductStore } from '../../../store/simulation/useProductStore'; import { useActionHandler } from '../actions/useActionHandler'; import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore'; +import { determineExecutionOrder } from './functions/determineExecutionOrder'; function Simulator() { const { products } = useProductStore(); @@ -11,237 +12,21 @@ function Simulator() { useEffect(() => { if (!isPlaying || isReset) return; - + const executionOrder = determineExecutionOrder(products); executionOrder.map(point => { const action = 'actions' in point ? point.actions[0] : point.action; handleAction(action); }); - }, [products, handleAction, isPlaying, isReset]); + }, [products, isPlaying, isReset]); - function determineExecutionOrder(products: productsSchema): PointsScheme[] { - // Create maps for all events and points - const eventMap = new Map(); - const pointMap = new Map(); - const allPoints: PointsScheme[] = []; + return ( - // First pass: collect all points - products.forEach(product => { - product.eventDatas.forEach(event => { - eventMap.set(event.modelUuid, event); + <> - if (event.type === 'transfer') { - event.points.forEach(point => { - pointMap.set(point.uuid, point); - allPoints.push(point); - }); - } else if (event.type === 'vehicle' || - event.type === 'machine' || - event.type === 'storageUnit') { - pointMap.set(event.point.uuid, event.point); - allPoints.push(event.point); - } else if (event.type === 'roboticArm') { - pointMap.set(event.point.uuid, event.point); - allPoints.push(event.point); - } - }); - }); + - // Build dependency graphs - const graph = new Map(); - const reverseGraph = new Map(); - const allTriggeredPoints = new Set(); - - allPoints.forEach(point => { - const triggers = extractTriggersFromPoint(point); - const dependencies: string[] = []; - - triggers.forEach(trigger => { - const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid; - if (targetUuid && pointMap.has(targetUuid)) { - dependencies.push(targetUuid); - allTriggeredPoints.add(targetUuid); - - if (!reverseGraph.has(targetUuid)) { - reverseGraph.set(targetUuid, []); - } - reverseGraph.get(targetUuid)!.push(point.uuid); - } - }); - - graph.set(point.uuid, dependencies); - }); - - // Identify root points (points that trigger others but aren't triggered themselves) - const rootPoints = allPoints - .filter(point => !allTriggeredPoints.has(point.uuid)) - .filter(point => { - // Only include roots that actually have triggers pointing FROM them - const triggers = extractTriggersFromPoint(point); - return triggers.some(t => t.triggeredAsset?.triggeredPoint?.pointUuid); - }); - - // If no root points found but we have triggered points, find the earliest triggers - if (rootPoints.length === 0 && allTriggeredPoints.size > 0) { - // This handles cases where we have circular dependencies - // but still want to include the triggered points - const minTriggerCount = Math.min( - ...Array.from(allTriggeredPoints) - .map(uuid => (graph.get(uuid) || []).length) - ); - const potentialRoots = Array.from(allTriggeredPoints) - .filter(uuid => (graph.get(uuid) || []).length === minTriggerCount); - - rootPoints.push(...potentialRoots.map(uuid => pointMap.get(uuid)!)); - } - - // Topological sort only for triggered points - const visited = new Set(); - const temp = new Set(); - const order: string[] = []; - let hasCycle = false; - - function visit(node: string) { - if (temp.has(node)) { - hasCycle = true; - return; - } - if (visited.has(node)) return; - - temp.add(node); - - const dependencies = reverseGraph.get(node) || []; - for (const dep of dependencies) { - visit(dep); - } - - temp.delete(node); - visited.add(node); - order.push(node); - } - - // Start processing from root points - rootPoints.forEach(root => visit(root.uuid)); - - // Convert UUIDs back to points and filter out untriggered points - const triggeredPoints = order - .map(uuid => pointMap.get(uuid)!) - .filter(point => allTriggeredPoints.has(point.uuid) || - rootPoints.some(root => root.uuid === point.uuid)); - - return triggeredPoints; - } - - function determineExecutionSequences(products: productsSchema): PointsScheme[][] { - // Create maps for all points - const pointMap = new Map(); - const allPoints: PointsScheme[] = []; - - // First pass: collect all points - products.forEach(product => { - product.eventDatas.forEach(event => { - if (event.type === 'transfer') { - event.points.forEach(point => { - pointMap.set(point.uuid, point); - allPoints.push(point); - }); - } else if (event.type === 'vehicle' || - event.type === 'machine' || - event.type === 'storageUnit' || - event.type === 'roboticArm') { - pointMap.set(event.point.uuid, event.point); - allPoints.push(event.point); - } - }); - }); - - // Build complete dependency graph - const dependencyGraph = new Map(); - const reverseDependencyGraph = new Map(); - const triggeredPoints = new Set(); - - allPoints.forEach(point => { - const triggers = extractTriggersFromPoint(point); - const dependencies: string[] = []; - - triggers.forEach(trigger => { - const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid; - if (targetUuid && pointMap.has(targetUuid)) { - dependencies.push(targetUuid); - triggeredPoints.add(targetUuid); - - if (!reverseDependencyGraph.has(targetUuid)) { - reverseDependencyGraph.set(targetUuid, []); - } - reverseDependencyGraph.get(targetUuid)!.push(point.uuid); - } - }); - - dependencyGraph.set(point.uuid, dependencies); - }); - - // Identify independent root points (points that trigger others but aren't triggered themselves) - const rootPoints = allPoints.filter(point => { - const hasOutgoingTriggers = extractTriggersFromPoint(point).some( - t => t.triggeredAsset?.triggeredPoint?.pointUuid - ); - return hasOutgoingTriggers && !triggeredPoints.has(point.uuid); - }); - - // For each root point, build its complete trigger chain - const executionSequences: PointsScheme[][] = []; - - function buildSequence(startUuid: string): PointsScheme[] { - const sequence: PointsScheme[] = []; - const visited = new Set(); - - function traverse(uuid: string) { - if (visited.has(uuid)) return; - visited.add(uuid); - - const point = pointMap.get(uuid); - if (point) { - sequence.push(point); - } - - // Follow forward dependencies - const nextPoints = dependencyGraph.get(uuid) || []; - nextPoints.forEach(nextUuid => traverse(nextUuid)); - } - - traverse(startUuid); - return sequence; - } - - // Build sequences for all root points - rootPoints.forEach(root => { - executionSequences.push(buildSequence(root.uuid)); - }); - - // Handle any triggered points not reachable from roots (isolated chains) - const processedPoints = new Set( - executionSequences.flat().map(p => p.uuid) - ); - - allPoints.forEach(point => { - if (triggeredPoints.has(point.uuid) && !processedPoints.has(point.uuid)) { - executionSequences.push(buildSequence(point.uuid)); - } - }); - - return executionSequences; - } - - function extractTriggersFromPoint(point: PointsScheme): TriggerSchema[] { - if ('actions' in point) { - return point.actions.flatMap(action => action.triggers); - } else if ('action' in point) { - return point.action.triggers; - } - return []; - } - - return <>; + ); } export default Simulator; \ No newline at end of file