@@ -260,100 +260,97 @@ export const SimulationProvider: React.FC<SimulationProviderProps> = ({
260260 } ;
261261
262262 const initialize : SimulationContextValue [ "initialize" ] = ( { seed, dt } ) => {
263- const currentState = getState ( ) ;
263+ // Use functional update to ensure we see the latest state at processing time.
264+ // This prevents race conditions where another action (like run()) modifies
265+ // state between when we read it and when the update is processed.
266+ setStateValues ( ( prev ) => {
267+ if ( prev . state === "Running" ) {
268+ // Don't overwrite if another action changed state to Running.
269+ // We can't throw inside a state updater, so we just return unchanged.
270+ // The UI should prevent this case, but this is a safety guard.
271+ return prev ;
272+ }
264273
265- if ( currentState . state === "Running" ) {
266- throw new Error (
267- "Cannot initialize simulation while it is running. Please reset first." ,
268- ) ;
269- }
274+ try {
275+ const sdcpn = getSDCPN ( ) ;
276+
277+ // Check SDCPN validity before building simulation
278+ const checkResult = checkSDCPN ( sdcpn ) ;
279+
280+ if ( ! checkResult . isValid ) {
281+ const firstError = checkResult . itemDiagnostics [ 0 ] ! ;
282+ const firstDiagnostic = firstError . diagnostics [ 0 ] ! ;
283+ const errorMessage =
284+ typeof firstDiagnostic . messageText === "string"
285+ ? firstDiagnostic . messageText
286+ : ts . flattenDiagnosticMessageText (
287+ firstDiagnostic . messageText ,
288+ "\n" ,
289+ ) ;
290+
291+ return {
292+ ...prev ,
293+ simulation : null ,
294+ state : "Error" as const ,
295+ error : `TypeScript error in ${ firstError . itemType } (${ firstError . itemId } ): ${ errorMessage } ` ,
296+ errorItemId : firstError . itemId ,
297+ } ;
298+ }
270299
271- try {
272- const sdcpn = getSDCPN ( ) ;
273-
274- // Check SDCPN validity before building simulation
275- const checkResult = checkSDCPN ( sdcpn ) ;
276-
277- if ( ! checkResult . isValid ) {
278- const firstError = checkResult . itemDiagnostics [ 0 ] ! ;
279- const firstDiagnostic = firstError . diagnostics [ 0 ] ! ;
280- const errorMessage =
281- typeof firstDiagnostic . messageText === "string"
282- ? firstDiagnostic . messageText
283- : ts . flattenDiagnosticMessageText (
284- firstDiagnostic . messageText ,
285- "\n" ,
286- ) ;
287-
288- setStateValues ( {
289- ...currentState ,
290- simulation : null ,
291- state : "Error" as const ,
292- error : `TypeScript error in ${ firstError . itemType } (${ firstError . itemId } ): ${ errorMessage } ` ,
293- errorItemId : firstError . itemId ,
294- } ) ;
295- } else {
296300 // Build the simulation instance using stored initialMarking and parameterValues
297301 const simulationInstance = buildSimulation ( {
298302 sdcpn,
299- initialMarking : currentState . initialMarking ,
300- parameterValues : currentState . parameterValues ,
303+ initialMarking : prev . initialMarking ,
304+ parameterValues : prev . parameterValues ,
301305 seed,
302306 dt,
303307 } ) ;
304308
305- setStateValues ( {
306- ...currentState ,
309+ return {
310+ ...prev ,
307311 simulation : simulationInstance ,
308312 state : "Paused" ,
309313 error : null ,
310314 errorItemId : null ,
311315 currentlyViewedFrame : 0 ,
312- } ) ;
316+ } ;
317+ } catch ( error ) {
318+ // eslint-disable-next-line no-console
319+ console . error ( "Error initializing simulation:" , error ) ;
320+
321+ return {
322+ ...prev ,
323+ simulation : null ,
324+ state : "Error" ,
325+ error :
326+ error instanceof Error
327+ ? error . message
328+ : "Unknown error occurred during initialization" ,
329+ errorItemId : error instanceof SDCPNItemError ? error . itemId : null ,
330+ } ;
313331 }
314- } catch ( error ) {
315- // eslint-disable-next-line no-console
316- console . error ( "Error initializing simulation:" , error ) ;
317-
318- setStateValues ( {
319- ...currentState ,
320- simulation : null ,
321- state : "Error" ,
322- error :
323- error instanceof Error
324- ? error . message
325- : "Unknown error occurred during initialization" ,
326- errorItemId : error instanceof SDCPNItemError ? error . itemId : null ,
327- } ) ;
328- }
332+ } ) ;
329333 } ;
330334
331335 const run : SimulationContextValue [ "run" ] = ( ) => {
332- const currentState = getState ( ) ;
333-
334- if ( currentState . state === "Running" ) {
335- throw new Error ( "Cannot run simulation: Simulation is already running." ) ;
336- }
337-
338- if ( ! currentState . simulation ) {
339- throw new Error (
340- "Cannot run simulation: No simulation initialized. Call initialize() first." ,
341- ) ;
342- }
336+ // Use functional update to ensure we see the latest state at processing time.
337+ // This prevents race conditions where state changes between validation and update.
338+ setStateValues ( ( prev ) => {
339+ // Guard against invalid states - return unchanged if we can't run
340+ if ( prev . state === "Running" ) {
341+ return prev ; // Already running
342+ }
343343
344- if ( currentState . state === "Error" ) {
345- throw new Error (
346- "Cannot run simulation: Simulation is in error state. Please reset." ,
347- ) ;
348- }
344+ if ( ! prev . simulation ) {
345+ return prev ; // No simulation initialized
346+ }
349347
350- if ( currentState . state === "Complete" ) {
351- throw new Error (
352- "Cannot run simulation: Simulation is complete. Please reset to run again." ,
353- ) ;
354- }
348+ if ( prev . state === "Error" || prev . state === "Complete" ) {
349+ return prev ; // Can't run from these states
350+ }
355351
356- setStateValues ( ( prev ) => ( { ...prev , state : "Running" } ) ) ;
352+ return { ...prev , state : "Running" } ;
353+ } ) ;
357354 } ;
358355
359356 const pause : SimulationContextValue [ "pause" ] = ( ) => {
0 commit comments