first commit

This commit is contained in:
2025-06-10 15:28:23 +05:30
commit e22a2dc275
699 changed files with 100382 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
import { useCallback } from "react";
import { useSceneContext } from "../../../../scene/sceneContext";
export function useDefaultHandler() {
const { materialStore } = useSceneContext();
const { getMaterialById } = materialStore();
const defaultLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleDefault = useCallback((action: ConveyorAction, materialId?: string) => {
if (!action || action.actionType !== 'default' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
defaultLogStatus(material.materialName, `performed Default action`);
}, [getMaterialById]);
return {
handleDefault,
};
}

View File

@@ -0,0 +1,112 @@
import { useCallback, useEffect, useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore";
import { useSceneContext } from "../../../../scene/sceneContext";
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 { materialStore } = useSceneContext();
const { setIsPaused } = materialStore();
const activeDelays = useRef<Map<string, DelayInstance>>(new Map());
const lastUpdateTimeRef = useRef<number>(performance.now());
const cleanupDelay = useCallback(() => {
activeDelays.current.clear();
}, []);
useEffect(() => {
if (isReset) {
cleanupDelay();
}
}, [isReset, cleanupDelay]);
const delayLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
};
useFrame(() => {
const currentTime = performance.now();
const deltaTime = currentTime - lastUpdateTimeRef.current;
lastUpdateTimeRef.current = currentTime;
const completedDelays: string[] = [];
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.lastUpdateTime = currentTime;
delay.elapsedTime = 0;
} else if (!delay.isPaused && isPlaying) {
delay.elapsedTime += deltaTime * speed;
if (delay.elapsedTime >= delay.initialDelay) {
if (delay.materialId) {
delayLogStatus(delay.materialId, `Delay completed`);
setIsPaused(delay.materialId, false);
}
completedDelays.push(key);
}
}
});
completedDelays.forEach(key => {
activeDelays.current.delete(key);
});
});
const handleDelay = useCallback((action: ConveyorAction, materialId?: string) => {
if (!action || action.actionType !== 'delay' || !materialId) return;
const delayMs = (action.delay || 0) * 1000;
if (delayMs <= 0) return;
const key = materialId ? `${materialId}-${action.actionUuid}` : action.actionUuid;
if (activeDelays.current.has(key)) {
activeDelays.current.delete(key);
}
const now = performance.now();
activeDelays.current.set(key, {
initialDelay: delayMs,
delayEndTime: now + delayMs,
materialId,
action,
isPaused: false,
remainingTime: 0,
lastUpdateTime: now,
elapsedTime: 0
});
delayLogStatus(materialId, `Started ${delayMs / 1000}s delay`);
setIsPaused(materialId, true);
}, [isPlaying]);
useEffect(() => {
return () => {
cleanupDelay();
};
}, [cleanupDelay]);
return {
handleDelay,
cleanupDelay
};
}

View File

@@ -0,0 +1,28 @@
import { useCallback } from "react";
import { useSceneContext } from "../../../../scene/sceneContext";
export function useDespawnHandler() {
const { materialStore } = useSceneContext();
const { getMaterialById, removeMaterial, setEndTime } = materialStore();
const deSpawnLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleDespawn = useCallback((action: ConveyorAction, materialId?: string) => {
if (!action || action.actionType !== 'despawn' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
setEndTime(material.materialId, performance.now());
removeMaterial(material.materialId);
deSpawnLogStatus(material.materialName, `Despawned`);
}, [getMaterialById, removeMaterial]);
return {
handleDespawn,
};
}

View File

@@ -0,0 +1,235 @@
import { useCallback, useEffect, useState } from "react";
import * as THREE from 'three';
import { useFrame } from "@react-three/fiber";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { usePlayButtonStore, useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from "../../../../../store/usePlayButtonStore";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
interface SpawnInstance {
lastSpawnTime: number | null;
startTime: number;
spawnCount: number;
params: {
material: string;
intervalMs: number;
totalCount: number;
action: ConveyorAction;
};
pauseStartTime: number;
remainingTime: number;
isPaused: boolean;
}
export function useSpawnHandler() {
const { materialStore, conveyorStore } = useSceneContext();
const { addMaterial } = materialStore();
const { getConveyorById } = conveyorStore();
const { getModelUuidByActionUuid, getPointUuidByActionUuid } = useProductStore();
const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore();
const { speed } = useAnimationPlaySpeed();
const { isReset } = useResetButtonStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const [activeSpawns, setActiveSpawns] = useState<Map<string, SpawnInstance>>(new Map());
const getConveyorPausedState = useCallback((action: ConveyorAction) => {
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return false;
const conveyor = getConveyorById(modelUuid);
if (!conveyor) return false;
return conveyor.isPaused;
}, [getConveyorById, getModelUuidByActionUuid, selectedProduct.productUuid]);
const shouldPauseSpawn = useCallback((action: ConveyorAction) => {
return isPaused || getConveyorPausedState(action);
}, [isPaused, getConveyorPausedState]);
const clearAllSpawns = useCallback(() => {
setActiveSpawns(new Map());
}, []);
useEffect(() => {
if (isReset) {
clearAllSpawns();
}
}, [isReset, clearAllSpawns]);
const spawnLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const createNewMaterial = useCallback((materialType: string, action: ConveyorAction) => {
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
const pointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid || !pointUuid) return;
const currentTime = performance.now();
const newMaterial: MaterialSchema = {
materialId: THREE.MathUtils.generateUUID(),
materialName: `${materialType}-${Date.now()}`,
materialType: materialType,
isActive: false,
isVisible: true,
isPaused: false,
isRendered: true,
startTime: currentTime,
current: {
modelUuid: modelUuid,
pointUuid: pointUuid,
actionUuid: action.actionUuid
},
};
if (action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid &&
action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid &&
action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid
) {
newMaterial.next = {
modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid,
pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid,
}
}
addMaterial(newMaterial);
return newMaterial;
}, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productUuid]);
useFrame(() => {
const currentTime = performance.now();
const completedActions: string[] = [];
let hasChanges = false;
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 / speed) - (currentTime - spawn.startTime));
} else {
spawn.remainingTime = Math.max(0, (spawn.params.intervalMs / speed) - (currentTime - spawn.lastSpawnTime));
}
spawn.pauseStartTime = currentTime;
spawn.isPaused = true;
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;
}
});
if (isPlaying && !isReset && !isPaused) {
activeSpawns.forEach((spawn, actionUuid) => {
if (spawn.isPaused) return;
const { material, intervalMs, totalCount, action } = spawn.params;
const adjustedInterval = intervalMs / speed;
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.materialName, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
}
spawn.lastSpawnTime = currentTime;
spawn.spawnCount = 1;
hasChanges = true;
if (totalCount <= 1) {
completedActions.push(actionUuid);
}
}
return;
}
// 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.materialName, `[${timeSinceLast.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`);
}
spawn.lastSpawnTime = currentTime;
spawn.spawnCount = count;
hasChanges = true;
if (count >= totalCount) {
completedActions.push(actionUuid);
}
}
}
});
}
if (hasChanges || completedActions.length > 0) {
setActiveSpawns(prevSpawns => {
const newSpawns = new Map(prevSpawns);
completedActions.forEach(actionUuid => {
newSpawns.delete(actionUuid);
});
return newSpawns;
});
}
});
const handleSpawn = useCallback((action: ConveyorAction) => {
if (!action || action.actionType !== 'spawn') return;
const { material, spawnInterval = 0, spawnCount = 1, actionUuid } = action;
const intervalMs = spawnInterval * 1000;
setActiveSpawns(prevSpawns => {
const newSpawns = new Map(prevSpawns);
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;
});
}, []);
useEffect(() => {
return () => {
clearAllSpawns();
};
}, [clearAllSpawns]);
return {
handleSpawn,
clearAllSpawns
};
}

View File

@@ -0,0 +1,27 @@
import { useCallback } from "react";
import { useSceneContext } from "../../../../scene/sceneContext";
export function useSwapHandler() {
const { materialStore } = useSceneContext();
const { getMaterialById, setMaterial } = materialStore();
const swapLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleSwap = useCallback((action: ConveyorAction, materialId?: string) => {
if (!action || action.actionType !== 'swap' || !materialId) return;
const { material: newMaterialType } = action;
const material = getMaterialById(materialId);
if (!material) return;
setMaterial(material.materialId, newMaterialType);
swapLogStatus(material.materialName, `Swapped to ${newMaterialType}`);
}, [getMaterialById, setMaterial]);
return {
handleSwap,
};
}

View File

@@ -0,0 +1,74 @@
import { useEffect, useCallback } from "react";
import { useDefaultHandler } from "./actionHandler/useDefaultHandler";
import { useSpawnHandler } from "./actionHandler/useSpawnHandler";
import { useSwapHandler } from "./actionHandler/useSwapHandler";
import { useDelayHandler } from "./actionHandler/useDelayHandler";
import { useDespawnHandler } from "./actionHandler/useDespawnHandler";
export function useConveyorActions() {
const { handleDefault } = useDefaultHandler();
const { handleSpawn, clearAllSpawns } = useSpawnHandler();
const { handleSwap } = useSwapHandler();
const { handleDespawn } = useDespawnHandler();
const { handleDelay, cleanupDelay } = useDelayHandler();
const handleDefaultAction = useCallback((action: ConveyorAction, materialId?: string) => {
handleDefault(action, materialId);
}, [handleDefault]);
const handleSpawnAction = useCallback((action: ConveyorAction) => {
handleSpawn(action);
}, [handleSpawn]);
const handleSwapAction = useCallback((action: ConveyorAction, materialId?: string) => {
handleSwap(action, materialId);
}, [handleSwap]);
const handleDelayAction = useCallback((action: ConveyorAction, materialId?: string) => {
handleDelay(action, materialId);
}, [handleDelay]);
const handleDespawnAction = useCallback((action: ConveyorAction, materialId?: string) => {
handleDespawn(action, materialId)
}, [handleDespawn]);
const handleConveyorAction = useCallback((action: ConveyorAction, materialId?: string) => {
if (!action) return;
switch (action.actionType) {
case 'default':
handleDefaultAction(action);
break;
case 'spawn':
handleSpawnAction(action);
break;
case 'swap':
handleSwapAction(action, materialId);
break;
case 'delay':
handleDelayAction(action, materialId);
break;
case 'despawn':
handleDespawnAction(action, materialId);
break;
default:
console.warn(`Unknown conveyor action type: ${action.actionType}`);
}
}, [handleDefaultAction, handleSpawnAction, handleSwapAction, handleDelayAction, handleDespawnAction]);
const cleanup = useCallback(() => {
clearAllSpawns();
cleanupDelay();
}, [clearAllSpawns, cleanupDelay]);
useEffect(() => {
return () => {
cleanup();
};
}, [cleanup]);
return {
handleConveyorAction,
cleanup
};
}

View File

@@ -0,0 +1,41 @@
import { useCallback } from "react";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function useProcessHandler() {
const { materialStore, machineStore } = useSceneContext();
const { selectedProductStore } = useProductContext();
const { getMaterialById, setMaterial } = materialStore();
const { addCurrentAction } = machineStore();
const { getModelUuidByActionUuid } = useProductStore();
const { selectedProduct } = selectedProductStore();
const processLogStatus = (materialUuid: string, status: string) => {
echo.log(`${materialUuid}, ${status}`);
}
const handleProcess = useCallback((action: MachineAction, materialId?: string) => {
if (!action || action.actionType !== 'process' || !materialId) return;
const { swapMaterial: newMaterialType } = action;
const material = getMaterialById(materialId);
if (!material) return;
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return;
setMaterial(material.materialId, newMaterialType);
addCurrentAction(modelUuid, action.actionUuid, newMaterialType, material.materialId);
processLogStatus(material.materialName, `Swapped to ${newMaterialType}`);
processLogStatus(material.materialName, `starts Process action`);
}, [getMaterialById]);
return {
handleProcess
};
}

View File

@@ -0,0 +1,36 @@
import { useEffect, useCallback } from 'react';
import { useProcessHandler } from './actionHandler/useProcessHandler';
export function useMachineActions() {
const { handleProcess } = useProcessHandler();
const handleProcessAction = useCallback((action: MachineAction, materialId: string) => {
handleProcess(action, materialId);
}, [handleProcess]);
const handleMachineAction = useCallback((action: MachineAction, materialId: string) => {
if (!action) return;
switch (action.actionType) {
case 'process':
handleProcessAction(action, materialId);
break;
default:
console.warn(`Unknown machine action type: ${action.actionType}`);
}
}, [handleProcessAction]);
const cleanup = useCallback(() => {
}, []);
useEffect(() => {
return () => {
cleanup();
};
}, [cleanup]);
return {
handleMachineAction,
cleanup,
};
}

View File

@@ -0,0 +1,42 @@
import { useCallback } from "react";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function usePickAndPlaceHandler() {
const { materialStore, armBotStore } = useSceneContext();
const { selectedProductStore } = useProductContext();
const { getMaterialById } = materialStore();
const { addCurrentAction } = armBotStore();
const { getModelUuidByActionUuid } = useProductStore();
const { selectedProduct } = selectedProductStore();
const pickAndPlaceLogStatus = (materialUuid: string, status: string) => {
echo.warn(`${materialUuid}, ${status}`);
}
const handlePickAndPlace = useCallback((action: RoboticArmAction, materialId?: string) => {
if (!action || action.actionType !== 'pickAndPlace' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return;
addCurrentAction(
modelUuid,
action.actionUuid,
material.materialType,
material.materialId
);
pickAndPlaceLogStatus(material.materialName, `is going to be picked by armBot ${modelUuid}`);
}, [getMaterialById, getModelUuidByActionUuid, selectedProduct.productUuid, addCurrentAction]);
return {
handlePickAndPlace,
};
}

View File

@@ -0,0 +1,36 @@
import { useEffect, useCallback } from 'react';
import { usePickAndPlaceHandler } from './actionHandler/usePickAndPlaceHandler';
export function useRoboticArmActions() {
const { handlePickAndPlace } = usePickAndPlaceHandler();
const handlePickAndPlaceAction = useCallback((action: RoboticArmAction, materialId: string) => {
handlePickAndPlace(action, materialId);
}, [handlePickAndPlace]);
const handleRoboticArmAction = useCallback((action: RoboticArmAction, materialId: string) => {
if (!action) return;
switch (action.actionType) {
case 'pickAndPlace':
handlePickAndPlaceAction(action, materialId);
break;
default:
console.warn(`Unknown robotic arm action type: ${action.actionType}`);
}
}, [handlePickAndPlaceAction]);
const cleanup = useCallback(() => {
}, []);
useEffect(() => {
return () => {
cleanup();
};
}, [cleanup]);
return {
handleRoboticArmAction,
cleanup
};
}

View File

@@ -0,0 +1,333 @@
import { useCallback, useState, useEffect, useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function useRetrieveHandler() {
const { materialStore, armBotStore, vehicleStore, storageUnitStore } = useSceneContext();
const { selectedProductStore } = useProductContext();
const { addMaterial } = materialStore();
const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = useProductStore();
const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore();
const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore();
const { selectedProduct } = selectedProductStore();
const { getArmBotById, addCurrentAction } = armBotStore();
const { isPlaying } = usePlayButtonStore();
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore();
const [activeRetrievals, setActiveRetrievals] = useState<Map<string, { action: StorageAction, isProcessing: boolean, lastCheckTime: number }>>(new Map());
const retrievalTimeRef = useRef<Map<string, number>>(new Map());
const [initialDelayComplete, setInitialDelayComplete] = useState(false);
const delayTimerRef = useRef<NodeJS.Timeout | null>(null);
const retrieveLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const createNewMaterial = useCallback((materialId: string, materialType: string, action: StorageAction) => {
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
const pointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid || !pointUuid) return null;
const currentTime = performance.now();
if (action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid &&
action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid &&
action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid
) {
const newMaterial: MaterialSchema = {
materialId: materialId,
materialName: `${materialType}-${Date.now()}`,
materialType: materialType,
isActive: false,
isVisible: false,
isPaused: false,
isRendered: true,
startTime: currentTime,
previous: {
modelUuid: modelUuid,
pointUuid: pointUuid,
actionUuid: action.actionUuid
},
current: {
modelUuid: action.triggers[0]?.triggeredAsset.triggeredModel.modelUuid,
pointUuid: action.triggers[0]?.triggeredAsset.triggeredPoint.pointUuid,
actionUuid: action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid
},
};
addMaterial(newMaterial);
return newMaterial;
}
return null;
}, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productUuid]);
useEffect(() => {
if (isPlaying && !initialDelayComplete) {
delayTimerRef.current = setTimeout(() => {
setInitialDelayComplete(true);
}, 3000);
}
return () => {
if (delayTimerRef.current) {
clearTimeout(delayTimerRef.current);
}
};
}, [isPlaying, initialDelayComplete]);
useEffect(() => {
if (isReset || isPaused) {
setInitialDelayComplete(false);
if (delayTimerRef.current) {
clearTimeout(delayTimerRef.current);
}
}
}, [isReset, isPaused]);
useFrame(() => {
if (!isPlaying || isPaused || isReset || !initialDelayComplete) return;
const currentTime = performance.now();
const completedActions: string[] = [];
let hasChanges = false;
activeRetrievals.forEach((retrieval, actionUuid) => {
if (retrieval.isProcessing) return;
const storageUnit = getStorageUnitById(
getModelUuidByActionUuid(selectedProduct.productUuid, retrieval.action.actionUuid) ?? ''
);
if (!storageUnit || storageUnit.currentLoad <= 0) {
completedActions.push(actionUuid);
hasChanges = true;
return;
}
if (retrieval.action.triggers.length === 0 || !retrieval.action.triggers[0]?.triggeredAsset) {
return;
}
const triggeredModel = getEventByModelUuid(
selectedProduct.productUuid,
retrieval.action.triggers[0]?.triggeredAsset.triggeredModel.modelUuid
);
if (!triggeredModel) return;
let isIdle = false;
if (triggeredModel.type === 'roboticArm') {
const armBot = getArmBotById(triggeredModel.modelUuid);
isIdle = (armBot && !armBot.isActive && armBot.state === 'idle' && !armBot.currentAction) || false;
if (!armBot) return;
if (!retrievalTimeRef.current.has(actionUuid) && isIdle) {
retrievalTimeRef.current.set(actionUuid, currentTime);
return;
}
const idleStartTime = retrievalTimeRef.current.get(actionUuid);
const minIdleTimeBeforeFirstRetrieval = 5000 / speed;
const minDelayBetweenRetrievals = 5000 / speed;
const canProceedFirstRetrieval = idleStartTime !== undefined &&
(currentTime - idleStartTime) >= minIdleTimeBeforeFirstRetrieval;
const lastRetrievalTime = retrievalTimeRef.current.get(`${actionUuid}_last`) ?? null;
const canProceedSubsequent = lastRetrievalTime === null ||
(currentTime - lastRetrievalTime) >= minDelayBetweenRetrievals;
const canProceed = lastRetrievalTime === null ? canProceedFirstRetrieval : canProceedSubsequent;
if (isIdle && canProceed) {
setActiveRetrievals(prev => {
const newRetrievals = new Map(prev);
newRetrievals.set(actionUuid, {
...retrieval,
isProcessing: true,
lastCheckTime: currentTime
});
return newRetrievals;
});
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) {
if (retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid) {
const action = getActionByUuid(selectedProduct.productUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid);
if (action && action.triggers.length > 0 && action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid) {
const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset.triggeredModel.modelUuid);
if (model) {
if (model.type === 'vehicle') {
const vehicle = getVehicleById(model.modelUuid);
if (vehicle && !vehicle.isActive && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
const material = createNewMaterial(
lastMaterial.materialId,
lastMaterial.materialType,
storageUnit.point.action
);
if (material) {
addCurrentAction(
triggeredModel.modelUuid,
retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? '',
material.materialType,
material.materialId
);
retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`);
}
}
} else {
const material = createNewMaterial(
lastMaterial.materialId,
lastMaterial.materialType,
storageUnit.point.action
);
if (material) {
addCurrentAction(
triggeredModel.modelUuid,
retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? '',
material.materialType,
material.materialId
);
retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`);
}
}
setActiveRetrievals(prev => {
const newRetrievals = new Map(prev);
newRetrievals.set(actionUuid, {
...retrieval,
isProcessing: false,
lastCheckTime: currentTime,
});
return newRetrievals;
});
retrievalTimeRef.current.set(actionUuid, currentTime);
}
}
}
}
} else if (!isIdle) {
retrievalTimeRef.current.delete(actionUuid);
}
} else if (triggeredModel.type === 'vehicle') {
const vehicle = getVehicleById(triggeredModel.modelUuid);
isIdle = (vehicle && !vehicle.isActive && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) || false;
if (!vehicle) return;
const loadDuration = vehicle.point.action.unLoadDuration;
let minDelayBetweenRetrievals = (loadDuration * 1000) / speed;
const minIdleTimeBeforeFirstRetrieval = 3000 / speed;
if (!retrievalTimeRef.current.has(actionUuid) && isIdle) {
retrievalTimeRef.current.set(actionUuid, currentTime);
return;
}
const idleStartTime = retrievalTimeRef.current.get(actionUuid);
const lastRetrievalTime = retrievalTimeRef.current.get(`${actionUuid}_last`) ?? null;
const canProceedFirstRetrieval = idleStartTime !== undefined &&
(currentTime - idleStartTime) >= minIdleTimeBeforeFirstRetrieval;
const canProceedSubsequent = lastRetrievalTime === null ||
(currentTime - lastRetrievalTime) >= minDelayBetweenRetrievals;
const canProceed = lastRetrievalTime === null ? canProceedFirstRetrieval : canProceedSubsequent;
if (isIdle && canProceed) {
setActiveRetrievals(prev => {
const newRetrievals = new Map(prev);
newRetrievals.set(actionUuid, {
...retrieval,
isProcessing: true,
lastCheckTime: currentTime
});
return newRetrievals;
});
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) {
if (vehicle?.currentLoad < vehicle.point.action.loadCapacity) {
const material = createNewMaterial(
lastMaterial.materialId,
lastMaterial.materialType,
storageUnit.point.action
);
if (material) {
removeLastMaterial(storageUnit.modelUuid);
updateCurrentLoad(storageUnit.modelUuid, -1)
incrementVehicleLoad(vehicle.modelUuid, 1);
addCurrentMaterial(vehicle.modelUuid, material.materialType, material.materialId);
retrieveLogStatus(material.materialName, `is picked by ${vehicle.modelName}`);
}
}
}
setActiveRetrievals(prev => {
const newRetrievals = new Map(prev);
newRetrievals.set(actionUuid, {
...retrieval,
isProcessing: false,
lastCheckTime: currentTime,
});
return newRetrievals;
});
retrievalTimeRef.current.set(actionUuid, currentTime);
retrievalTimeRef.current.set(`${actionUuid}_last`, currentTime);
} else if (!isIdle) {
retrievalTimeRef.current.delete(actionUuid);
retrievalTimeRef.current.delete(`${actionUuid}_last`);
}
}
});
if (hasChanges || completedActions.length > 0) {
setActiveRetrievals(prev => {
const newRetrievals = new Map(prev);
completedActions.forEach(actionUuid => newRetrievals.delete(actionUuid));
return newRetrievals;
});
}
});
const handleRetrieve = useCallback((action: StorageAction) => {
if (!action || action.actionType !== 'retrieve') return;
setActiveRetrievals(prev => {
const newRetrievals = new Map(prev);
newRetrievals.set(action.actionUuid, {
action,
isProcessing: false,
lastCheckTime: performance.now(),
});
return newRetrievals;
});
}, []);
useEffect(() => {
if (isReset) {
setActiveRetrievals(new Map());
setInitialDelayComplete(false);
}
}, [isReset]);
return {
handleRetrieve,
};
}

View File

@@ -0,0 +1,39 @@
import { useCallback } from "react";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function useStoreHandler() {
const { materialStore, storageUnitStore } = useSceneContext();
const { getMaterialById, removeMaterial, setEndTime } = materialStore();
const { addCurrentMaterial, updateCurrentLoad } = storageUnitStore();
const { getModelUuidByActionUuid } = useProductStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const storeLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleStore = useCallback((action: StorageAction, materialId?: string) => {
if (!action || action.actionType !== 'store' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return;
setEndTime(material.materialId, performance.now());
removeMaterial(material.materialId);
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
updateCurrentLoad(modelUuid, 1);
storeLogStatus(material.materialName, `performed Store action`);
}, [getMaterialById]);
return {
handleStore,
};
}

View File

@@ -0,0 +1,45 @@
import { useEffect, useCallback } from 'react';
import { useStoreHandler } from './actionHandler/useStoreHandler';
import { useRetrieveHandler } from './actionHandler/useRetrieveHandler';
export function useStorageActions() {
const { handleStore } = useStoreHandler();
const { handleRetrieve } = useRetrieveHandler();
const handleStoreAction = useCallback((action: StorageAction, materialId: string) => {
handleStore(action, materialId);
}, [handleStore]);
const handleRetrieveAction = useCallback((action: StorageAction) => {
handleRetrieve(action);
}, [handleRetrieve]);
const handleStorageAction = useCallback((action: StorageAction, materialId: string) => {
if (!action) return;
switch (action.actionType) {
case 'store':
handleStoreAction(action, materialId);
break;
case 'retrieve':
handleRetrieveAction(action);
break;
default:
console.warn(`Unknown storage action type: ${action.actionType}`);
}
}, [handleStoreAction]);
const cleanup = useCallback(() => {
}, []);
useEffect(() => {
return () => {
cleanup();
};
}, [cleanup]);
return {
handleStorageAction,
cleanup
};
}

View File

@@ -0,0 +1,67 @@
import {
usePlayButtonStore,
useResetButtonStore,
} from "../../../store/usePlayButtonStore";
import { useConveyorActions } from "./conveyor/useConveyorActions";
import { useMachineActions } from "./machine/useMachineActions";
import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions";
import { useStorageActions } from "./storageUnit/useStorageUnitActions";
import { useVehicleActions } from "./vehicle/useVehicleActions";
import { useCallback, useEffect } from "react";
export function useActionHandler() {
const { isReset } = useResetButtonStore();
const { isPlaying } = usePlayButtonStore();
const { handleConveyorAction, cleanup: cleanupConveyor } = useConveyorActions();
const { handleVehicleAction, cleanup: cleanupVehicle } = useVehicleActions();
const { handleRoboticArmAction, cleanup: cleanupRoboticArm } = useRoboticArmActions();
const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions();
const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions();
const handleAction = useCallback((action: Action, materialId?: string) => {
if (!action) return;
try {
switch (action.actionType) {
case 'default': case 'spawn': case 'swap': case 'delay': case 'despawn':
handleConveyorAction(action as ConveyorAction, materialId as string);
break;
case 'travel':
handleVehicleAction(action as VehicleAction, materialId as string);
break;
case 'pickAndPlace':
handleRoboticArmAction(action as RoboticArmAction, materialId as string);
break;
case 'process':
handleMachineAction(action as MachineAction, materialId as string);
break;
case 'store': case 'retrieve':
handleStorageAction(action as StorageAction, materialId as string);
break;
default:
console.warn(`Unknown action type: ${(action as Action).actionType}`);
}
} catch (error) {
echo.error("Failed to handle action");
console.error("Error handling action:", error);
}
}, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction,]);
const cleanup = useCallback(() => {
cleanupConveyor();
cleanupVehicle();
cleanupRoboticArm();
cleanupMachine();
cleanupStorage();
}, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage,]);
useEffect(() => {
return () => {
cleanup();
};
}, [cleanup, isReset, isPlaying]);
return {
handleAction,
cleanup,
};
}

View File

@@ -0,0 +1,38 @@
import { useCallback } from "react";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function useTravelHandler() {
const { materialStore, vehicleStore } = useSceneContext();
const { selectedProductStore } = useProductContext();
const { getMaterialById } = materialStore();
const { getModelUuidByActionUuid } = useProductStore();
const { selectedProduct } = selectedProductStore();
const { incrementVehicleLoad, addCurrentMaterial } = vehicleStore();
const travelLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleTravel = useCallback((action: VehicleAction, materialId?: string) => {
if (!action || action.actionType !== 'travel' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return;
incrementVehicleLoad(modelUuid, 1);
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
travelLogStatus(material.materialName, `is triggering travel from ${modelUuid}`);
}, [addCurrentMaterial, getMaterialById, getModelUuidByActionUuid, incrementVehicleLoad, selectedProduct.productUuid]);
return {
handleTravel,
};
}

View File

@@ -0,0 +1,36 @@
import { useEffect, useCallback } from 'react';
import { useTravelHandler } from './actionHandler/useTravelHandler';
export function useVehicleActions() {
const { handleTravel } = useTravelHandler();
const handleTravelAction = useCallback((action: VehicleAction, materialId: string) => {
handleTravel(action, materialId);
}, [handleTravel]);
const handleVehicleAction = useCallback((action: VehicleAction, materialId: string) => {
if (!action) return;
switch (action.actionType) {
case 'travel':
handleTravelAction(action, materialId);
break;
default:
console.warn(`Unknown vehicle action type: ${action.actionType}`);
}
}, [handleTravelAction]);
const cleanup = useCallback(() => {
}, []);
useEffect(() => {
return () => {
cleanup();
};
}, [cleanup]);
return {
handleVehicleAction,
cleanup
};
}