From 31561428ef9c8468ad1587112a4fd85069ed350c Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 17 Apr 2025 18:22:27 +0530 Subject: [PATCH] feat: Add immer for state management and define simulation store with event schemas --- app/package-lock.json | 18 +- app/package.json | 1 + app/src/store/useSimulationStore.ts | 465 ++++++++++++++++++++++++++++ 3 files changed, 481 insertions(+), 3 deletions(-) create mode 100644 app/src/store/useSimulationStore.ts diff --git a/app/package-lock.json b/app/package-lock.json index 5d92f68..fa04c9f 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -30,6 +30,7 @@ "glob": "^11.0.0", "gsap": "^3.12.5", "html2canvas": "^1.4.1", + "immer": "^10.1.1", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", @@ -12746,9 +12747,10 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -18010,6 +18012,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/react-dev-utils/node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/react-dev-utils/node_modules/loader-utils": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", diff --git a/app/package.json b/app/package.json index e555d5f..66158c0 100644 --- a/app/package.json +++ b/app/package.json @@ -25,6 +25,7 @@ "glob": "^11.0.0", "gsap": "^3.12.5", "html2canvas": "^1.4.1", + "immer": "^10.1.1", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", diff --git a/app/src/store/useSimulationStore.ts b/app/src/store/useSimulationStore.ts new file mode 100644 index 0000000..aaa1697 --- /dev/null +++ b/app/src/store/useSimulationStore.ts @@ -0,0 +1,465 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; + +interface AssetEventSchema { + modelUuid: string; + modelName: string; + position: [number, number, number]; + rotation: [number, number, number]; + state: "idle" | "running" | "stopped" | "disabled" | "error"; +} + +interface TriggerSchema { + triggerUuid: string; + triggerName: string; + triggerType: "onComplete" | "onStart" | "onStop" | "delay" | "onError"; + delay: number; + triggeredAsset: { + triggeredModel: { modelName: string, modelUuid: string }; + triggeredAction: { actionName: string, actionUuid: string }; + } | null; +} + +interface TransferPointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { + actionUuid: string; + actionName: string; + actionType: "default" | "spawn" | "swap" | "despawn"; + material: string | "inherit"; + delay: number | "inherit"; + spawnInterval: number | "inherit"; + spawnCount: number | "inherit"; + triggers: TriggerSchema[]; + }[]; +} + +interface VehiclePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { + actionUuid: string; + actionName: string; + actionType: "travel"; + material: string; + unLoadDuration: number; + loadCapacity: number; + pickUpPoint: { x: number; y: number, z: number } | {}; + unLoadPoint: { x: number; y: number, z: number } | {}; + triggers: TriggerSchema[]; + }[]; +} + +interface RoboticArmPointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { + actionUuid: string; + actionName: string; + actionType: "pickAndPlace"; + process: { startPoint: string; endPoint: string }; + triggers: TriggerSchema[]; + }[]; +} + +interface MachinePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { + actionUuid: string; + actionName: string; + actionType: "process"; + processTime: number; + swapMaterial: string; + triggers: TriggerSchema[]; + }[]; +} + +interface StoragePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { + actionUuid: string; + actionName: string; + actionType: "storage"; + materials: { materialName: string; materialId: string; quantity: number }[]; + storageCapacity: number; + }[]; +} + +interface TransferEventSchema extends AssetEventSchema { + type: "transfer"; + speed: number; + points: TransferPointSchema[]; +} + +interface VehicleSchemaEvent extends AssetEventSchema { + type: "vehicle"; + speed: number; + point: VehiclePointSchema; +} + +interface RoboticArmSchemaEvent extends AssetEventSchema { + type: "roboticArm"; + speed: number; + point: RoboticArmPointSchema; +} + +interface MachineSchemaEvent extends AssetEventSchema { + type: "machine"; + point: MachinePointSchema; +} + +interface StorageSchemaEvent extends AssetEventSchema { + type: "storageUnit"; + point: StoragePointSchema; +} + +type EventsSchema = TransferEventSchema | VehicleSchemaEvent | RoboticArmSchemaEvent | MachineSchemaEvent | StorageSchemaEvent | []; + +type productsSchema = { + productName: string; + productId: string; + eventsData: EventsSchema[]; +}[] + +type Store = { + products: productsSchema; + + // Product-level actions + addProduct: (productName: string, productId: string) => void; + removeProduct: (productId: string) => void; + updateProduct: (productId: string, updates: Partial<{ productName: string; eventsData: EventsSchema[] }>) => void; + + // Event-level actions + addEventToProduct: (productId: string, event: EventsSchema) => void; + removeEventFromProduct: (productId: string, modelUuid: string) => void; + updateEventInProduct: (productId: string, modelUuid: string, updates: Partial) => void; + + // Point-level actions (for transfer, vehicle, etc.) + addPointToEvent: (productId: string, modelUuid: string, point: TransferPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void; + removePointFromEvent: (productId: string, modelUuid: string, pointUuid: string) => void; + updatePointInEvent: ( + productId: string, + modelUuid: string, + pointUuid: string, + updates: Partial + ) => void; + + // Action-level actions + addActionToPoint: ( + productId: string, + modelUuid: string, + pointUuid: string, + action: TransferPointSchema['actions'][0] | VehiclePointSchema['actions'][0] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['actions'][0] | StoragePointSchema['actions'][0] + ) => void; + removeActionFromPoint: (productId: string, modelUuid: string, pointUuid: string, actionUuid: string) => void; + updateActionInPoint: ( + productId: string, + modelUuid: string, + pointUuid: string, + actionUuid: string, + updates: Partial + ) => void; + + // Trigger-level actions + addTriggerToAction: ( + productId: string, + modelUuid: string, + pointUuid: string, + actionUuid: string, + trigger: TriggerSchema + ) => void; + removeTriggerFromAction: (productId: string, modelUuid: string, pointUuid: string, actionUuid: string, triggerUuid: string) => void; + updateTriggerInAction: ( + productId: string, + modelUuid: string, + pointUuid: string, + actionUuid: string, + triggerUuid: string, + updates: Partial + ) => void; + + // Helper functions + getProductById: (productId: string) => { productName: string; productId: string; eventsData: EventsSchema[] } | undefined; + getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined; + getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => TransferPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined; +}; + +export const useProductStore = create()( + immer((set, get) => ({ + products: [], + activeProductId: null, + + // Product-level actions + addProduct: (productName, productId) => { + set((state) => { + const newProduct = { + productName, + productId: productId, + eventsData: [] + }; + state.products.push(newProduct); + }); + }, + + removeProduct: (productId) => { + set((state) => { + state.products = state.products.filter(p => p.productId !== productId); + }); + }, + + updateProduct: (productId, updates) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + Object.assign(product, updates); + } + }); + }, + + // Event-level actions + addEventToProduct: (productId, event) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + product.eventsData.push(event); + } + }); + }, + + removeEventFromProduct: (productId, modelUuid) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + product.eventsData = product.eventsData.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); + } + }); + }, + + updateEventInProduct: (productId, modelUuid, updates) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event) { + Object.assign(event, updates); + } + } + }); + }, + + // Point-level actions + addPointToEvent: (productId, modelUuid, point) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + (event as TransferEventSchema).points.push(point as TransferPointSchema); + } else if (event && 'point' in event) { + (event as VehicleSchemaEvent | RoboticArmSchemaEvent | MachineSchemaEvent | StorageSchemaEvent).point = point as any; + } + } + }); + }, + + removePointFromEvent: (productId, modelUuid, pointUuid) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + (event as TransferEventSchema).points = (event as TransferEventSchema).points.filter(p => p.uuid !== pointUuid); + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + // For events with single point, we can't remove it, only reset to empty + // You might want to handle this differently + } + } + }); + }, + + updatePointInEvent: (productId, modelUuid, pointUuid, updates) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + const point = (event as TransferEventSchema).points.find(p => p.uuid === pointUuid); + if (point) { + Object.assign(point, updates); + } + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + Object.assign((event as any).point, updates); + } + } + }); + }, + + // Action-level actions + addActionToPoint: (productId, modelUuid, pointUuid, action) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + const point = (event as TransferEventSchema).points.find(p => p.uuid === pointUuid); + if (point) { + point.actions.push(action as any); + } + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + (event as any).point.actions.push(action); + } + } + }); + }, + + removeActionFromPoint: (productId, modelUuid, pointUuid, actionUuid) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + const point = (event as TransferEventSchema).points.find(p => p.uuid === pointUuid); + if (point) { + point.actions = point.actions.filter(a => a.actionUuid !== actionUuid); + } + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + (event as any).point.actions = (event as any).point.actions.filter((a: any) => a.actionUuid !== actionUuid); + } + } + }); + }, + + updateActionInPoint: (productId, modelUuid, pointUuid, actionUuid, updates) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + const point = (event as TransferEventSchema).points.find(p => p.uuid === pointUuid); + if (point) { + const action = point.actions.find(a => a.actionUuid === actionUuid); + if (action) { + Object.assign(action, updates); + } + } + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + const action = (event as any).point.actions.find((a: any) => a.actionUuid === actionUuid); + if (action) { + Object.assign(action, updates); + } + } + } + }); + }, + + // Trigger-level actions + addTriggerToAction: (productId, modelUuid, pointUuid, actionUuid, trigger) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + const point = (event as TransferEventSchema).points.find(p => p.uuid === pointUuid); + if (point) { + const action = point.actions.find(a => a.actionUuid === actionUuid); + if (action && 'triggers' in action) { + action.triggers.push(trigger); + } + } + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + const action = (event as any).point.actions.find((a: any) => a.actionUuid === actionUuid); + if (action && 'triggers' in action) { + action.triggers.push(trigger); + } + } + } + }); + }, + + removeTriggerFromAction: (productId, modelUuid, pointUuid, actionUuid, triggerUuid) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + const point = (event as TransferEventSchema).points.find(p => p.uuid === pointUuid); + if (point) { + const action = point.actions.find(a => a.actionUuid === actionUuid); + if (action && 'triggers' in action) { + action.triggers = action.triggers.filter(t => t.triggerUuid !== triggerUuid); + } + } + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + const action = (event as any).point.actions.find((a: any) => a.actionUuid === actionUuid); + if (action && 'triggers' in action) { + action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); + } + } + } + }); + }, + + updateTriggerInAction: (productId, modelUuid, pointUuid, actionUuid, triggerUuid, updates) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + const point = (event as TransferEventSchema).points.find(p => p.uuid === pointUuid); + if (point) { + const action = point.actions.find(a => a.actionUuid === actionUuid); + if (action && 'triggers' in action) { + const trigger = action.triggers.find(t => t.triggerUuid === triggerUuid); + if (trigger) { + Object.assign(trigger, updates); + } + } + } + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + const action = (event as any).point.actions.find((a: any) => a.actionUuid === actionUuid); + if (action && 'triggers' in action) { + const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); + if (trigger) { + Object.assign(trigger, updates); + } + } + } + } + }); + }, + + // Helper functions + getProductById: (productId) => { + return get().products.find(p => p.productId === productId); + }, + + getEventByModelUuid: (productId, modelUuid) => { + const product = get().products.find(p => p.productId === productId); + if (product) { + return product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + } + return undefined; + }, + + getPointByUuid: (productId, modelUuid, pointUuid) => { + const product = get().products.find(p => p.productId === productId); + if (product) { + const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + if (event && 'points' in event) { + return (event as TransferEventSchema).points.find(p => p.uuid === pointUuid); + } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { + return (event as any).point; + } + } + return undefined; + } + })) +); \ No newline at end of file