feat: Introduce simulation module with time management, entity action handlers for conveyors and storage units, and analysis components.
This commit is contained in:
@@ -76,6 +76,9 @@ type SceneContextValue = {
|
|||||||
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
|
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
|
||||||
craneEventManagerRef: React.RefObject<CraneEventManagerState>;
|
craneEventManagerRef: React.RefObject<CraneEventManagerState>;
|
||||||
|
|
||||||
|
simulationTimeRef: React.MutableRefObject<number>;
|
||||||
|
getSimulationTime: () => number;
|
||||||
|
|
||||||
clearStores: () => void;
|
clearStores: () => void;
|
||||||
|
|
||||||
layout: "Main Layout" | "Comparison Layout";
|
layout: "Main Layout" | "Comparison Layout";
|
||||||
@@ -134,6 +137,10 @@ export function SceneProvider({
|
|||||||
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
|
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
|
||||||
const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] });
|
const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] });
|
||||||
|
|
||||||
|
// Simulation Time
|
||||||
|
const simulationTimeRef = useRef<number>(0);
|
||||||
|
const getSimulationTime = () => simulationTimeRef.current;
|
||||||
|
|
||||||
const clearStores = useMemo(
|
const clearStores = useMemo(
|
||||||
() => () => {
|
() => () => {
|
||||||
scene.current = null;
|
scene.current = null;
|
||||||
@@ -165,6 +172,7 @@ export function SceneProvider({
|
|||||||
collabUsersStore.getState().clearCollabUsers();
|
collabUsersStore.getState().clearCollabUsers();
|
||||||
humanEventManagerRef.current.humanStates = [];
|
humanEventManagerRef.current.humanStates = [];
|
||||||
craneEventManagerRef.current.craneStates = [];
|
craneEventManagerRef.current.craneStates = [];
|
||||||
|
simulationTimeRef.current = 0;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
versionStore,
|
versionStore,
|
||||||
@@ -225,6 +233,8 @@ export function SceneProvider({
|
|||||||
collabUsersStore,
|
collabUsersStore,
|
||||||
humanEventManagerRef,
|
humanEventManagerRef,
|
||||||
craneEventManagerRef,
|
craneEventManagerRef,
|
||||||
|
simulationTimeRef,
|
||||||
|
getSimulationTime,
|
||||||
clearStores,
|
clearStores,
|
||||||
layout,
|
layout,
|
||||||
layoutType,
|
layoutType,
|
||||||
|
|||||||
@@ -2,28 +2,30 @@ import { useCallback } from "react";
|
|||||||
import { useSceneContext } from "../../../../scene/sceneContext";
|
import { useSceneContext } from "../../../../scene/sceneContext";
|
||||||
|
|
||||||
export function useDespawnHandler() {
|
export function useDespawnHandler() {
|
||||||
const { materialStore } = useSceneContext();
|
const { materialStore, getSimulationTime } = useSceneContext();
|
||||||
const { getMaterialById, removeMaterial, setEndTime, clearLocations } = materialStore();
|
const { getMaterialById, removeMaterial, setEndTime, clearLocations } = materialStore();
|
||||||
|
|
||||||
const deSpawnLogStatus = (materialUuid: string, status: string) => {
|
const deSpawnLogStatus = (materialUuid: string, status: string) => {
|
||||||
echo.info(`${materialUuid}, ${status}`);
|
echo.info(`${materialUuid}, ${status}`);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleDespawn = useCallback((action: ConveyorAction, materialId?: string) => {
|
const handleDespawn = useCallback(
|
||||||
if (!action || action.actionType !== 'despawn' || !materialId) return;
|
(action: ConveyorAction, materialId?: string) => {
|
||||||
|
if (!action || action.actionType !== "despawn" || !materialId) return;
|
||||||
|
|
||||||
const material = getMaterialById(materialId);
|
const material = getMaterialById(materialId);
|
||||||
if (!material) return;
|
if (!material) return;
|
||||||
|
|
||||||
setEndTime(material.materialId, performance.now());
|
setEndTime(material.materialId, getSimulationTime());
|
||||||
removeMaterial(material.materialId);
|
removeMaterial(material.materialId);
|
||||||
clearLocations(material.materialId);
|
clearLocations(material.materialId);
|
||||||
|
|
||||||
deSpawnLogStatus(material.materialName, `Despawned`);
|
deSpawnLogStatus(material.materialName, `Despawned`);
|
||||||
|
},
|
||||||
}, [getMaterialById, removeMaterial]);
|
[getMaterialById, removeMaterial]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleDespawn,
|
handleDespawn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface SpawnInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useSpawnHandler() {
|
export function useSpawnHandler() {
|
||||||
const { materialStore, conveyorStore, productStore } = useSceneContext();
|
const { materialStore, conveyorStore, productStore, getSimulationTime } = useSceneContext();
|
||||||
const { addMaterial } = materialStore();
|
const { addMaterial } = materialStore();
|
||||||
const { getConveyorById } = conveyorStore();
|
const { getConveyorById } = conveyorStore();
|
||||||
const { getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct } = productStore();
|
const { getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct } = productStore();
|
||||||
@@ -69,7 +69,7 @@ export function useSpawnHandler() {
|
|||||||
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
||||||
const pointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
const pointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
||||||
if (!modelUuid || !pointUuid) return;
|
if (!modelUuid || !pointUuid) return;
|
||||||
const currentTime = performance.now();
|
const currentTime = getSimulationTime();
|
||||||
|
|
||||||
const newMaterial: MaterialSchema = {
|
const newMaterial: MaterialSchema = {
|
||||||
materialId: THREE.MathUtils.generateUUID(),
|
materialId: THREE.MathUtils.generateUUID(),
|
||||||
@@ -101,11 +101,11 @@ export function useSpawnHandler() {
|
|||||||
addMaterial(newMaterial);
|
addMaterial(newMaterial);
|
||||||
return newMaterial;
|
return newMaterial;
|
||||||
},
|
},
|
||||||
[addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productUuid]
|
[selectedProduct.productUuid]
|
||||||
);
|
);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
const currentTime = performance.now();
|
const currentTime = getSimulationTime();
|
||||||
const completedActions: string[] = [];
|
const completedActions: string[] = [];
|
||||||
let hasChanges = false;
|
let hasChanges = false;
|
||||||
|
|
||||||
@@ -114,9 +114,9 @@ export function useSpawnHandler() {
|
|||||||
|
|
||||||
if (isPausedNow && !spawn.isPaused) {
|
if (isPausedNow && !spawn.isPaused) {
|
||||||
if (spawn.lastSpawnTime === null) {
|
if (spawn.lastSpawnTime === null) {
|
||||||
spawn.remainingTime = Math.max(0, spawn.params.intervalMs / speed - (currentTime - spawn.startTime));
|
spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.startTime));
|
||||||
} else {
|
} else {
|
||||||
spawn.remainingTime = Math.max(0, spawn.params.intervalMs / speed - (currentTime - spawn.lastSpawnTime));
|
spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.lastSpawnTime));
|
||||||
}
|
}
|
||||||
spawn.pauseStartTime = currentTime;
|
spawn.pauseStartTime = currentTime;
|
||||||
spawn.isPaused = true;
|
spawn.isPaused = true;
|
||||||
@@ -140,7 +140,8 @@ export function useSpawnHandler() {
|
|||||||
if (spawn.isPaused) return;
|
if (spawn.isPaused) return;
|
||||||
|
|
||||||
const { material, intervalMs, totalCount, action } = spawn.params;
|
const { material, intervalMs, totalCount, action } = spawn.params;
|
||||||
const adjustedInterval = intervalMs / speed;
|
// intervalMs is in simulation time, and currentTime is simulation time. No speed adjustment needed.
|
||||||
|
const adjustedInterval = intervalMs;
|
||||||
const isFirstSpawn = spawn.lastSpawnTime === null;
|
const isFirstSpawn = spawn.lastSpawnTime === null;
|
||||||
|
|
||||||
// First spawn
|
// First spawn
|
||||||
@@ -211,7 +212,7 @@ export function useSpawnHandler() {
|
|||||||
|
|
||||||
newSpawns.set(actionUuid, {
|
newSpawns.set(actionUuid, {
|
||||||
lastSpawnTime: null,
|
lastSpawnTime: null,
|
||||||
startTime: performance.now(),
|
startTime: getSimulationTime(),
|
||||||
spawnCount: 0,
|
spawnCount: 0,
|
||||||
params: {
|
params: {
|
||||||
material,
|
material,
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { useSceneContext } from "../../../../scene/sceneContext";
|
|||||||
import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager";
|
import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager";
|
||||||
|
|
||||||
export function useRetrieveHandler() {
|
export function useRetrieveHandler() {
|
||||||
const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, craneStore, productStore, humanStore, assetStore, humanEventManagerRef } = useSceneContext();
|
const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, craneStore, productStore, humanStore, assetStore, humanEventManagerRef, getSimulationTime } =
|
||||||
|
useSceneContext();
|
||||||
const { addMaterial } = materialStore();
|
const { addMaterial } = materialStore();
|
||||||
const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid, selectedProduct } = productStore();
|
const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid, selectedProduct } = productStore();
|
||||||
const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore();
|
const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore();
|
||||||
@@ -40,7 +41,7 @@ export function useRetrieveHandler() {
|
|||||||
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
||||||
const pointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
const pointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
||||||
if (!modelUuid || !pointUuid) return null;
|
if (!modelUuid || !pointUuid) return null;
|
||||||
const currentTime = performance.now();
|
const currentTime = getSimulationTime();
|
||||||
|
|
||||||
const newMaterial: MaterialSchema = {
|
const newMaterial: MaterialSchema = {
|
||||||
materialId: materialId,
|
materialId: materialId,
|
||||||
@@ -95,7 +96,7 @@ export function useRetrieveHandler() {
|
|||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (!isPlaying || isPaused || isReset || !initialDelayComplete) return;
|
if (!isPlaying || isPaused || isReset || !initialDelayComplete) return;
|
||||||
|
|
||||||
const currentTime = performance.now();
|
const currentTime = getSimulationTime();
|
||||||
const completedActions: string[] = [];
|
const completedActions: string[] = [];
|
||||||
let hasChanges = false;
|
let hasChanges = false;
|
||||||
|
|
||||||
@@ -135,8 +136,8 @@ export function useRetrieveHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const idleStartTime = retrievalTimeRef.current.get(actionUuid);
|
const idleStartTime = retrievalTimeRef.current.get(actionUuid);
|
||||||
const minIdleTimeBeforeFirstRetrieval = 5000 / speed;
|
const minIdleTimeBeforeFirstRetrieval = 5000;
|
||||||
const minDelayBetweenRetrievals = 5000 / speed;
|
const minDelayBetweenRetrievals = 5000;
|
||||||
|
|
||||||
const canProceedFirstRetrieval = idleStartTime !== undefined && currentTime - idleStartTime >= minIdleTimeBeforeFirstRetrieval;
|
const canProceedFirstRetrieval = idleStartTime !== undefined && currentTime - idleStartTime >= minIdleTimeBeforeFirstRetrieval;
|
||||||
|
|
||||||
@@ -215,8 +216,8 @@ export function useRetrieveHandler() {
|
|||||||
if (!vehicle) return;
|
if (!vehicle) return;
|
||||||
|
|
||||||
const loadDuration = vehicle.point.action.unLoadDuration;
|
const loadDuration = vehicle.point.action.unLoadDuration;
|
||||||
let minDelayBetweenRetrievals = (loadDuration * 1000) / speed;
|
let minDelayBetweenRetrievals = loadDuration * 1000;
|
||||||
const minIdleTimeBeforeFirstRetrieval = 3000 / speed;
|
const minIdleTimeBeforeFirstRetrieval = 3000;
|
||||||
|
|
||||||
if (!retrievalTimeRef.current.has(actionUuid) && isIdle) {
|
if (!retrievalTimeRef.current.has(actionUuid) && isIdle) {
|
||||||
retrievalTimeRef.current.set(actionUuid, currentTime);
|
retrievalTimeRef.current.set(actionUuid, currentTime);
|
||||||
@@ -535,7 +536,7 @@ export function useRetrieveHandler() {
|
|||||||
newRetrievals.set(action.actionUuid, {
|
newRetrievals.set(action.actionUuid, {
|
||||||
action,
|
action,
|
||||||
isProcessing: false,
|
isProcessing: false,
|
||||||
lastCheckTime: performance.now(),
|
lastCheckTime: getSimulationTime(),
|
||||||
});
|
});
|
||||||
return newRetrievals;
|
return newRetrievals;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ function Analyzer() {
|
|||||||
const { speed } = useAnimationPlaySpeed();
|
const { speed } = useAnimationPlaySpeed();
|
||||||
|
|
||||||
const { setAnalysis, setAnalyzing, analysis } = analysisStore();
|
const { setAnalysis, setAnalyzing, analysis } = analysisStore();
|
||||||
|
const { getSimulationTime } = useSceneContext();
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// COMPREHENSIVE TRACKING REFS FOR PERFORMANCE METRICS
|
// COMPREHENSIVE TRACKING REFS FOR PERFORMANCE METRICS
|
||||||
@@ -245,49 +246,18 @@ function Analyzer() {
|
|||||||
setAnalyzing(false);
|
setAnalyzing(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulation Time Tracking
|
|
||||||
const simulationTimeRef = useRef<number>(0);
|
|
||||||
const lastSpeedUpdateRef = useRef<number>(Date.now());
|
|
||||||
const prevSpeedRef = useRef<number>(speed); // Initialize with current speed
|
|
||||||
|
|
||||||
// Reset accumulated time when simulation stops/starts
|
// Reset accumulated time when simulation stops/starts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
resetAllRefs();
|
resetAllRefs();
|
||||||
simulationTimeRef.current = 0;
|
|
||||||
lastSpeedUpdateRef.current = Date.now();
|
|
||||||
} else {
|
} else {
|
||||||
// Reset start time when simulation starts
|
// Reset start time when simulation starts
|
||||||
startTimeRef.current = new Date().toISOString();
|
startTimeRef.current = new Date().toISOString();
|
||||||
simulationTimeRef.current = 0;
|
|
||||||
lastSpeedUpdateRef.current = Date.now();
|
|
||||||
}
|
}
|
||||||
}, [isPlaying]);
|
}, [isPlaying]);
|
||||||
|
|
||||||
// Track accumulated simulation time on speed change
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isPlaying) {
|
|
||||||
prevSpeedRef.current = speed;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const deltaReal = now - lastSpeedUpdateRef.current;
|
|
||||||
|
|
||||||
// Add time segment using previous speed
|
|
||||||
simulationTimeRef.current += deltaReal * Math.max(1, prevSpeedRef.current);
|
|
||||||
|
|
||||||
lastSpeedUpdateRef.current = now;
|
|
||||||
prevSpeedRef.current = speed; // Update for next segment
|
|
||||||
}, [speed, isPlaying]);
|
|
||||||
|
|
||||||
const getSimulationDuration = () => {
|
const getSimulationDuration = () => {
|
||||||
if (!isPlaying) return simulationTimeRef.current;
|
return getSimulationTime();
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const deltaReal = now - lastSpeedUpdateRef.current;
|
|
||||||
// Current accumulating segment uses current speed
|
|
||||||
return simulationTimeRef.current + deltaReal * Math.max(1, speed);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import SimulationAnalysis from "./analysis/simulationAnalysis";
|
|||||||
import { useSceneContext } from "../scene/sceneContext";
|
import { useSceneContext } from "../scene/sceneContext";
|
||||||
import HeatMap from "./heatMap/heatMap";
|
import HeatMap from "./heatMap/heatMap";
|
||||||
import Analyzer from "./analyzer/analyzer";
|
import Analyzer from "./analyzer/analyzer";
|
||||||
|
import TimeManager from "./simulator/TimeManager";
|
||||||
|
|
||||||
function Simulation() {
|
function Simulation() {
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
@@ -64,6 +65,8 @@ function Simulation() {
|
|||||||
|
|
||||||
<Simulator />
|
<Simulator />
|
||||||
|
|
||||||
|
<TimeManager />
|
||||||
|
|
||||||
<Analyzer />
|
<Analyzer />
|
||||||
|
|
||||||
<SimulationAnalysis />
|
<SimulationAnalysis />
|
||||||
|
|||||||
112
app/src/modules/simulation/simulator/TimeManager.tsx
Normal file
112
app/src/modules/simulation/simulator/TimeManager.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { useRef, useEffect } from "react";
|
||||||
|
import { usePlayButtonStore, useAnimationPlaySpeed } from "../../../store/ui/usePlayButtonStore";
|
||||||
|
import { useSceneContext } from "../../scene/sceneContext";
|
||||||
|
|
||||||
|
const TimeManager = () => {
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { speed } = useAnimationPlaySpeed();
|
||||||
|
const { simulationTimeRef } = useSceneContext();
|
||||||
|
|
||||||
|
const lastSpeedUpdateRef = useRef<number>(Date.now());
|
||||||
|
const prevSpeedRef = useRef<number>(speed);
|
||||||
|
|
||||||
|
// Reset accumulated time when simulation stops
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying) {
|
||||||
|
simulationTimeRef.current = 0;
|
||||||
|
lastSpeedUpdateRef.current = Date.now();
|
||||||
|
} else {
|
||||||
|
lastSpeedUpdateRef.current = Date.now();
|
||||||
|
}
|
||||||
|
}, [isPlaying, simulationTimeRef]);
|
||||||
|
|
||||||
|
// Track accumulated simulation time
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying) {
|
||||||
|
prevSpeedRef.current = speed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up an interval to update time continuously
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
const deltaReal = now - lastSpeedUpdateRef.current;
|
||||||
|
|
||||||
|
// Add time segment using current speed
|
||||||
|
// We use interval instead of useFrame because TimeManager might run outside of Canvas or we want independent tick
|
||||||
|
// Actually, if we want frame-perfect sync, useFrame is better.
|
||||||
|
// But this component might not be inside Canvas in all setups (though we plan to put it there).
|
||||||
|
// Let's stick to useEffect loop for now to mirror the logic we had, or better yet, use requestAnimationFrame
|
||||||
|
|
||||||
|
simulationTimeRef.current += deltaReal * Math.max(1, speed);
|
||||||
|
lastSpeedUpdateRef.current = now;
|
||||||
|
}, 16); // ~60fps
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [speed, isPlaying, simulationTimeRef]);
|
||||||
|
|
||||||
|
// Handle speed changes accurately by updating before speed changes
|
||||||
|
// The interval handles the continuous update. This effect ensures we capture the exact moment speed changes if we want higher precision,
|
||||||
|
// but the interval driven approach with small steps is usually sufficient for "game time".
|
||||||
|
// Actually, "prevSpeedRef" logic was better for exact transitions.
|
||||||
|
|
||||||
|
// Let's refine the logic to match what we did in Analyzer:
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying) {
|
||||||
|
prevSpeedRef.current = speed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const deltaReal = now - lastSpeedUpdateRef.current;
|
||||||
|
|
||||||
|
// Add time segment using previous speed (before this update)
|
||||||
|
// Wait, if we use interval, we are continuously updating.
|
||||||
|
// If we mix interval and this effect, we double count.
|
||||||
|
|
||||||
|
// Let's ONLY use requestAnimationFrame (or a tight interval) to update time.
|
||||||
|
// And update refs.
|
||||||
|
}, [speed, isPlaying]); // This is tricky with intervals.
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Re-implementing correctly with requestAnimationFrame for smooth time
|
||||||
|
const TimeManagerRAF = () => {
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { speed } = useAnimationPlaySpeed();
|
||||||
|
const { simulationTimeRef } = useSceneContext();
|
||||||
|
|
||||||
|
const lastTimeRef = useRef<number>(Date.now());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying) {
|
||||||
|
simulationTimeRef.current = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTimeRef.current = Date.now();
|
||||||
|
let frameId: number;
|
||||||
|
|
||||||
|
const loop = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const deltaReal = now - lastTimeRef.current;
|
||||||
|
|
||||||
|
if (deltaReal > 0) {
|
||||||
|
simulationTimeRef.current += deltaReal * Math.max(1, speed);
|
||||||
|
lastTimeRef.current = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
frameId = requestAnimationFrame(loop);
|
||||||
|
};
|
||||||
|
|
||||||
|
frameId = requestAnimationFrame(loop);
|
||||||
|
|
||||||
|
return () => cancelAnimationFrame(frameId);
|
||||||
|
}, [isPlaying, speed, simulationTimeRef]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TimeManagerRAF;
|
||||||
Reference in New Issue
Block a user