v2 #82
|
@ -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 = () => {
|
|||
</div>
|
||||
{index < intervals.length - 1 && (
|
||||
<div
|
||||
className={`line ${
|
||||
progress >= ((index + 1) / totalSegments) * 100
|
||||
className={`line ${progress >= ((index + 1) / totalSegments) * 100
|
||||
? "filled"
|
||||
: ""
|
||||
}`}
|
||||
}`}
|
||||
></div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
|
|
@ -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<Map<string, DelayInstance>>(new Map());
|
||||
const lastUpdateTimeRef = useRef<number>(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(() => {
|
||||
|
|
|
@ -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<Map<string, SpawnInstance>>(new Map());
|
||||
const [activeSpawns, setActiveSpawns] = useState<Map<string, SpawnInstance>>(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;
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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<string, EventsSchema>();
|
||||
const pointMap = new Map<string, PointsScheme>();
|
||||
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<string, string[]>();
|
||||
const reverseGraph = new Map<string, string[]>();
|
||||
const allTriggeredPoints = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
const temp = new Set<string>();
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import { extractTriggersFromPoint } from "./extractTriggersFromPoint";
|
||||
|
||||
export function determineExecutionSequences(products: productsSchema): PointsScheme[][] {
|
||||
// Create maps for all points
|
||||
const pointMap = new Map<string, PointsScheme>();
|
||||
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<string, string[]>();
|
||||
const reverseDependencyGraph = new Map<string, string[]>();
|
||||
const triggeredPoints = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
|
||||
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;
|
||||
}
|
|
@ -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 [];
|
||||
}
|
|
@ -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<string, EventsSchema>();
|
||||
const pointMap = new Map<string, PointsScheme>();
|
||||
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<string, string[]>();
|
||||
const reverseGraph = new Map<string, string[]>();
|
||||
const allTriggeredPoints = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
const temp = new Set<string>();
|
||||
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<string, PointsScheme>();
|
||||
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<string, string[]>();
|
||||
const reverseDependencyGraph = new Map<string, string[]>();
|
||||
const triggeredPoints = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
|
||||
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;
|
Loading…
Reference in New Issue