Files
Dwinzo_dev/app/src/modules/simulation/process/processCreator.tsx

493 lines
14 KiB
TypeScript
Raw Normal View History

2025-04-01 18:54:04 +05:30
import React, {
useEffect,
useMemo,
useState,
useCallback,
useRef,
} from "react";
import { useSimulationStates } from "../../../store/store";
2025-04-01 18:54:04 +05:30
import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import {
2025-04-15 18:34:38 +05:30
ArmBotEventsSchema,
2025-04-01 18:54:04 +05:30
ConveyorEventsSchema,
VehicleEventsSchema,
} from "../../../types/simulationTypes";
2025-04-11 17:52:07 +05:30
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
2025-04-01 18:54:04 +05:30
// Type definitions
export interface PointAction {
uuid: string;
name: string;
type: string;
material: string;
delay: number | string;
spawnInterval: string | number;
isUsed: boolean;
}
2025-04-03 10:28:13 +05:30
2025-04-10 10:21:24 +05:30
export interface PointTrigger {
uuid: string;
bufferTime: number;
name: string;
type: string;
isUsed: boolean;
}
2025-04-15 18:34:38 +05:30
// Update the connections type in your interfaces
2025-04-01 18:54:04 +05:30
export interface PathPoint {
uuid: string;
position: [number, number, number];
actions: PointAction[];
2025-04-10 10:21:24 +05:30
triggers: PointTrigger[];
2025-04-01 18:54:04 +05:30
connections: {
2025-04-15 18:34:38 +05:30
targets: Array<{ modelUUID: string; pointUUID?: string }>;
2025-04-01 18:54:04 +05:30
};
}
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
export interface SimulationPath {
type: string;
2025-04-01 18:54:04 +05:30
modeluuid: string;
points: PathPoint[];
pathPosition: [number, number, number];
speed?: number;
2025-04-11 17:52:07 +05:30
isActive: boolean;
2025-04-01 18:54:04 +05:30
}
2025-04-15 18:34:38 +05:30
export interface ArmBot {
type: string;
modeluuid: string;
points: PathPoint[];
pathPosition: [number, number, number];
speed?: number;
isActive: boolean;
}
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
export interface Process {
id: string;
paths: SimulationPath[];
animationPath: THREE.Vector3[];
pointActions: PointAction[][];
2025-04-10 10:21:24 +05:30
pointTriggers: PointTrigger[][];
2025-04-01 18:54:04 +05:30
speed: number;
2025-04-11 17:52:07 +05:30
isActive: boolean;
2025-04-01 18:54:04 +05:30
}
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
interface ProcessCreatorProps {
onProcessesCreated: (processes: Process[]) => void;
}
// Convert event schemas to SimulationPath
function convertToSimulationPath(
2025-04-15 18:34:38 +05:30
path: ConveyorEventsSchema | VehicleEventsSchema | ArmBotEventsSchema
2025-04-01 18:54:04 +05:30
): SimulationPath {
const { modeluuid } = path;
2025-04-10 10:21:24 +05:30
// Normalized action handler
2025-04-01 18:54:04 +05:30
const normalizeAction = (action: any): PointAction => {
return { ...action }; // Return exact copy with no modifications
};
2025-04-10 10:21:24 +05:30
// Normalized trigger handler
const normalizeTrigger = (trigger: any): PointTrigger => {
return { ...trigger }; // Return exact copy with no modifications
};
2025-04-01 18:54:04 +05:30
if (path.type === "Conveyor") {
2025-04-03 10:28:13 +05:30
return {
type: path.type,
2025-04-03 10:28:13 +05:30
modeluuid,
points: path.points.map((point) => ({
uuid: point.uuid,
position: point.position,
2025-04-10 10:21:24 +05:30
actions: Array.isArray(point.actions)
? point.actions.map(normalizeAction)
: point.actions
2025-04-15 18:34:38 +05:30
? [normalizeAction(point.actions)]
: [],
2025-04-10 10:21:24 +05:30
triggers: Array.isArray(point.triggers)
? point.triggers.map(normalizeTrigger)
: point.triggers
2025-04-15 18:34:38 +05:30
? [normalizeTrigger(point.triggers)]
: [],
2025-04-01 18:54:04 +05:30
connections: {
2025-04-03 10:28:13 +05:30
targets: point.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
2025-04-01 18:54:04 +05:30
})),
},
2025-04-03 10:28:13 +05:30
})),
pathPosition: path.position,
speed:
typeof path.speed === "string"
? parseFloat(path.speed) || 1
: path.speed || 1,
2025-04-11 17:52:07 +05:30
isActive: false, // Added missing property
2025-04-03 10:28:13 +05:30
};
2025-04-15 18:34:38 +05:30
} else if (path.type === "ArmBot") {
return {
type: path.type,
modeluuid,
points: [
{
uuid: path.points.uuid,
position: path.points.position,
actions: Array.isArray(path.points.actions)
? path.points.actions.map(normalizeAction)
: path.points.actions
? [normalizeAction(path.points.actions)]
: [],
triggers: Array.isArray(path.points.triggers)
? path.points.triggers.map(normalizeTrigger)
: path.points.triggers
? [normalizeTrigger(path.points.triggers)]
: [],
connections: {
targets: path.points.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
pointUUID: target.pointUUID, // Include if available
})),
},
},
],
pathPosition: path.position,
speed: path.points.actions?.speed || 1,
isActive: false,
};
2025-04-01 18:54:04 +05:30
} else {
2025-04-10 10:21:24 +05:30
// For vehicle paths, handle the case where triggers might not exist
2025-04-03 10:28:13 +05:30
return {
type: path.type,
2025-04-03 10:28:13 +05:30
modeluuid,
points: [
{
uuid: path.points.uuid,
position: path.points.position,
actions: Array.isArray(path.points.actions)
? path.points.actions.map(normalizeAction)
2025-04-10 10:21:24 +05:30
: path.points.actions
2025-04-15 18:34:38 +05:30
? [normalizeAction(path.points.actions)]
: [],
2025-04-10 10:21:24 +05:30
triggers: [],
2025-04-03 10:28:13 +05:30
connections: {
targets: path.points.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
2025-04-03 10:28:13 +05:30
})),
},
},
],
pathPosition: path.position,
speed: path.points.speed || 1,
2025-04-11 17:52:07 +05:30
isActive: false,
2025-04-03 10:28:13 +05:30
};
2025-04-01 18:54:04 +05:30
}
}
// Helper function to create an empty process
const createEmptyProcess = (): Process => ({
id: `process-${Math.random().toString(36).substring(2, 11)}`,
paths: [],
animationPath: [],
pointActions: [],
2025-04-10 10:21:24 +05:30
pointTriggers: [], // Added point triggers array
2025-04-01 18:54:04 +05:30
speed: 1,
2025-04-11 17:52:07 +05:30
isActive: false,
2025-04-01 18:54:04 +05:30
});
// Enhanced connection checking function
function shouldReverseNextPath(
currentPath: SimulationPath,
nextPath: SimulationPath
): boolean {
if (nextPath.points.length !== 3) return false;
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
const currentLastPoint = currentPath.points[currentPath.points.length - 1];
const nextFirstPoint = nextPath.points[0];
const nextLastPoint = nextPath.points[nextPath.points.length - 1];
// Check if current last connects to next last (requires reversal)
const connectsToLast = currentLastPoint.connections.targets.some(
(target) =>
target.modelUUID === nextPath.modeluuid &&
2025-04-01 18:54:04 +05:30
nextLastPoint.connections.targets.some(
(t) => t.modelUUID === currentPath.modeluuid
2025-04-01 18:54:04 +05:30
)
);
// Check if current last connects to next first (no reversal needed)
const connectsToFirst = currentLastPoint.connections.targets.some(
(target) =>
target.modelUUID === nextPath.modeluuid &&
2025-04-01 18:54:04 +05:30
nextFirstPoint.connections.targets.some(
(t) => t.modelUUID === currentPath.modeluuid
2025-04-01 18:54:04 +05:30
)
);
// Only reverse if connected to last point and not to first point
return connectsToLast && !connectsToFirst;
}
2025-04-03 10:28:13 +05:30
// Check if a point has a spawn action
function hasSpawnAction(point: PathPoint): boolean {
return point.actions.some((action) => action.type.toLowerCase() === "spawn");
}
// Ensure spawn point is always at the beginning of the path
function ensureSpawnPointIsFirst(path: SimulationPath): SimulationPath {
if (path.points.length !== 3) return path;
// If the third point has spawn action and first doesn't, reverse the array
if (hasSpawnAction(path.points[2]) && !hasSpawnAction(path.points[0])) {
return {
...path,
points: [...path.points].reverse(),
};
}
return path;
}
2025-04-01 18:54:04 +05:30
// Updated path adjustment function
function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] {
2025-04-03 10:28:13 +05:30
if (paths.length < 1) return paths;
2025-04-01 18:54:04 +05:30
const adjustedPaths = [...paths];
2025-04-03 10:28:13 +05:30
// First ensure all paths have spawn points at the beginning
for (let i = 0; i < adjustedPaths.length; i++) {
adjustedPaths[i] = ensureSpawnPointIsFirst(adjustedPaths[i]);
}
// Then handle connections between paths
2025-04-01 18:54:04 +05:30
for (let i = 0; i < adjustedPaths.length - 1; i++) {
const currentPath = adjustedPaths[i];
const nextPath = adjustedPaths[i + 1];
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
if (shouldReverseNextPath(currentPath, nextPath)) {
const reversedPoints = [
nextPath.points[2],
nextPath.points[1],
nextPath.points[0],
];
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
adjustedPaths[i + 1] = {
...nextPath,
points: reversedPoints,
};
}
}
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
return adjustedPaths;
}
// Main hook for process creation
export function useProcessCreation() {
const { scene } = useThree();
const [processes, setProcesses] = useState<Process[]>([]);
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
const hasSpawnAction = useCallback((path: SimulationPath): boolean => {
if (path.type !== "Conveyor") return false;
2025-04-01 18:54:04 +05:30
return path.points.some((point) =>
point.actions.some((action) => action.type.toLowerCase() === "spawn")
);
}, []);
const createProcess = useCallback(
(paths: SimulationPath[]): Process => {
if (!paths || paths.length === 0) {
return createEmptyProcess();
}
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
const animationPath: THREE.Vector3[] = [];
const pointActions: PointAction[][] = [];
2025-04-10 10:21:24 +05:30
const pointTriggers: PointTrigger[][] = []; // Added point triggers collection
2025-04-01 18:54:04 +05:30
const processSpeed = paths[0]?.speed || 1;
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
for (const path of paths) {
for (const point of path.points) {
2025-04-10 10:21:24 +05:30
if (path.type === "Conveyor") {
const obj = scene.getObjectByProperty("uuid", point.uuid);
if (!obj) {
console.warn(`Object with UUID ${point.uuid} not found in scene`);
continue;
}
2025-04-03 10:28:13 +05:30
2025-04-10 10:21:24 +05:30
const position = obj.getWorldPosition(new THREE.Vector3());
animationPath.push(position.clone());
pointActions.push(point.actions);
pointTriggers.push(point.triggers); // Collect triggers for each point
}
2025-04-01 18:54:04 +05:30
}
}
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
return {
id: `process-${Math.random().toString(36).substring(2, 11)}`,
paths,
animationPath,
pointActions,
2025-04-10 10:21:24 +05:30
pointTriggers,
2025-04-01 18:54:04 +05:30
speed: processSpeed,
2025-04-11 17:52:07 +05:30
isActive: false,
2025-04-01 18:54:04 +05:30
};
},
[scene]
);
const getAllConnectedPaths = useCallback(
(
initialPath: SimulationPath,
allPaths: SimulationPath[],
visited: Set<string> = new Set()
): SimulationPath[] => {
const connectedPaths: SimulationPath[] = [];
const queue: SimulationPath[] = [initialPath];
visited.add(initialPath.modeluuid);
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
const pathMap = new Map<string, SimulationPath>();
allPaths.forEach((path) => pathMap.set(path.modeluuid, path));
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
while (queue.length > 0) {
const currentPath = queue.shift()!;
connectedPaths.push(currentPath);
// Process outgoing connections
for (const point of currentPath.points) {
for (const target of point.connections.targets) {
if (!visited.has(target.modelUUID)) {
const targetPath = pathMap.get(target.modelUUID);
2025-04-01 18:54:04 +05:30
if (targetPath) {
visited.add(target.modelUUID);
2025-04-01 18:54:04 +05:30
queue.push(targetPath);
}
}
}
}
// Process incoming connections
for (const [uuid, path] of pathMap) {
if (!visited.has(uuid)) {
const hasConnectionToCurrent = path.points.some((point) =>
point.connections.targets.some(
(t) => t.modelUUID === currentPath.modeluuid
2025-04-01 18:54:04 +05:30
)
);
if (hasConnectionToCurrent) {
visited.add(uuid);
queue.push(path);
}
}
}
}
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
return connectedPaths;
},
[]
);
const createProcessesFromPaths = useCallback(
(paths: SimulationPath[]): Process[] => {
if (!paths || paths.length === 0) return [];
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
const visited = new Set<string>();
const processes: Process[] = [];
const pathMap = new Map<string, SimulationPath>();
paths.forEach((path) => pathMap.set(path.modeluuid, path));
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
for (const path of paths) {
if (!visited.has(path.modeluuid) && hasSpawnAction(path)) {
const connectedPaths = getAllConnectedPaths(path, paths, visited);
const adjustedPaths = adjustPathPointsOrder(connectedPaths);
const process = createProcess(adjustedPaths);
processes.push(process);
}
}
2025-04-03 10:28:13 +05:30
2025-04-01 18:54:04 +05:30
return processes;
},
[createProcess, getAllConnectedPaths, hasSpawnAction]
);
return {
processes,
createProcessesFromPaths,
setProcesses,
};
}
const ProcessCreator: React.FC<ProcessCreatorProps> = React.memo(
({ onProcessesCreated }) => {
const { simulationStates } = useSimulationStates();
2025-04-01 18:54:04 +05:30
const { createProcessesFromPaths } = useProcessCreation();
const prevPathsRef = useRef<SimulationPath[]>([]);
const prevProcessesRef = useRef<Process[]>([]);
2025-04-11 17:52:07 +05:30
const { isPlaying } = usePlayButtonStore();
2025-04-01 18:54:04 +05:30
const convertedPaths = useMemo((): SimulationPath[] => {
if (!simulationStates) return [];
return simulationStates.map((path) =>
2025-04-01 18:54:04 +05:30
convertToSimulationPath(
2025-04-15 18:34:38 +05:30
path as
| ConveyorEventsSchema
| VehicleEventsSchema
| ArmBotEventsSchema
2025-04-01 18:54:04 +05:30
)
);
}, [simulationStates]);
2025-04-01 18:54:04 +05:30
2025-04-10 10:21:24 +05:30
// Enhanced dependency tracking that includes action and trigger types
2025-04-01 18:54:04 +05:30
const pathsDependency = useMemo(() => {
if (!convertedPaths) return null;
return convertedPaths.map((path) => ({
id: path.modeluuid,
2025-04-03 10:28:13 +05:30
// Track all action types for each point
actionSignature: path.points
.map((point, index) =>
point.actions.map((action) => `${index}-${action.type}`).join("|")
)
.join(","),
2025-04-10 10:21:24 +05:30
// Track all trigger types for each point
triggerSignature: path.points
.map((point, index) =>
point.triggers
.map((trigger) => `${index}-${trigger.type}`)
.join("|")
)
.join(","),
2025-04-01 18:54:04 +05:30
connections: path.points
.flatMap((p: PathPoint) =>
p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID)
2025-04-01 18:54:04 +05:30
)
.join(","),
2025-04-11 17:52:07 +05:30
isActive: false,
2025-04-01 18:54:04 +05:30
}));
}, [convertedPaths]);
2025-04-03 10:28:13 +05:30
// Force process recreation when paths change
2025-04-01 18:54:04 +05:30
useEffect(() => {
if (!convertedPaths || convertedPaths.length === 0) {
if (prevProcessesRef.current.length > 0) {
onProcessesCreated([]);
prevProcessesRef.current = [];
}
return;
}
2025-04-03 10:28:13 +05:30
// Always regenerate processes if the pathsDependency has changed
2025-04-10 10:21:24 +05:30
// This ensures action and trigger type changes will be detected
2025-04-01 18:54:04 +05:30
const newProcesses = createProcessesFromPaths(convertedPaths);
2025-04-03 10:28:13 +05:30
prevPathsRef.current = convertedPaths;
2025-04-10 10:21:24 +05:30
// Always update processes when action or trigger types change
2025-04-03 10:28:13 +05:30
onProcessesCreated(newProcesses);
prevProcessesRef.current = newProcesses;
2025-04-01 18:54:04 +05:30
}, [
2025-04-10 10:21:24 +05:30
pathsDependency, // This now includes action and trigger types
2025-04-01 18:54:04 +05:30
onProcessesCreated,
convertedPaths,
createProcessesFromPaths,
]);
return null;
}
);
export default ProcessCreator;