@@ -14,6 +14,8 @@ import {
1414 type InitialMarking ,
1515 SimulationContext ,
1616 type SimulationContextValue ,
17+ type SimulationFrameState ,
18+ type SimulationFrameStateDiff ,
1719 type SimulationState ,
1820} from "./simulation-context" ;
1921
@@ -24,7 +26,10 @@ type SimulationStateValues = {
2426 errorItemId : string | null ;
2527 parameterValues : Record < string , string > ;
2628 initialMarking : InitialMarking ;
27- currentlyViewedFrame : number ;
29+ /** Internal frame index for tracking which frame is being viewed */
30+ currentViewedFrameIndex : number | null ;
31+ /** Internal frame index for tracking which frame was previously viewed */
32+ previousViewedFrameIndex : number | null ;
2833 dt : number ;
2934} ;
3035
@@ -35,10 +40,87 @@ const initialStateValues: SimulationStateValues = {
3540 errorItemId : null ,
3641 parameterValues : { } ,
3742 initialMarking : new Map ( ) ,
38- currentlyViewedFrame : 0 ,
43+ currentViewedFrameIndex : null ,
44+ previousViewedFrameIndex : null ,
3945 dt : 0.01 ,
4046} ;
4147
48+ /**
49+ * Converts a simulation frame to a SimulationFrameState.
50+ */
51+ function buildFrameState (
52+ simulation : SimulationContextValue [ "simulation" ] ,
53+ frameIndex : number ,
54+ ) : SimulationFrameState | null {
55+ if ( ! simulation || simulation . frames . length === 0 ) {
56+ return null ;
57+ }
58+
59+ const frame = simulation . frames [ frameIndex ] ;
60+ if ( ! frame ) {
61+ return null ;
62+ }
63+
64+ const places : SimulationFrameState [ "places" ] = { } ;
65+ for ( const [ placeId , placeData ] of frame . places ) {
66+ places [ placeId ] = {
67+ tokenCount : placeData . count ,
68+ } ;
69+ }
70+
71+ const transitions : SimulationFrameState [ "transitions" ] = { } ;
72+ for ( const [ transitionId , transitionData ] of frame . transitions ) {
73+ transitions [ transitionId ] = {
74+ timeSinceLastFiring : transitionData . timeSinceLastFiring ,
75+ } ;
76+ }
77+
78+ return {
79+ number : frameIndex ,
80+ time : frame . time ,
81+ places,
82+ transitions,
83+ } ;
84+ }
85+
86+ /**
87+ * Computes the difference between two simulation frames.
88+ */
89+ function buildFrameStateDiff (
90+ currentFrame : SimulationFrameState ,
91+ comparedFrame : SimulationFrameState ,
92+ ) : SimulationFrameStateDiff {
93+ const places : SimulationFrameStateDiff [ "places" ] = { } ;
94+ for ( const placeId of Object . keys ( currentFrame . places ) ) {
95+ const currentTokenCount = currentFrame . places [ placeId ] ?. tokenCount ?? 0 ;
96+ const comparedTokenCount = comparedFrame . places [ placeId ] ?. tokenCount ?? 0 ;
97+ places [ placeId ] = {
98+ tokenCount : currentTokenCount - comparedTokenCount ,
99+ } ;
100+ }
101+
102+ const transitions : SimulationFrameStateDiff [ "transitions" ] = { } ;
103+ for ( const transitionId of Object . keys ( currentFrame . transitions ) ) {
104+ const currentTimeSinceLastFiring =
105+ currentFrame . transitions [ transitionId ] ?. timeSinceLastFiring ?? 0 ;
106+ // Count firings: if timeSinceLastFiring is 0, the transition just fired
107+ // We need to count how many times it fired between the two frames
108+ // For simplicity, we count 1 if it fired in current frame (timeSinceLastFiring === 0)
109+ // and the compared frame had a non-zero timeSinceLastFiring
110+ const justFired = currentTimeSinceLastFiring === 0 ;
111+ transitions [ transitionId ] = {
112+ firingCount : justFired ? 1 : 0 ,
113+ } ;
114+ }
115+
116+ return {
117+ currentFrame,
118+ comparedFrame,
119+ places,
120+ transitions,
121+ } ;
122+ }
123+
42124/**
43125 * Internal component that subscribes to simulation state changes
44126 * and shows notifications when appropriate.
@@ -190,7 +272,8 @@ export const SimulationProvider: React.FC<SimulationProviderProps> = ({
190272 state : "Paused" ,
191273 error : null ,
192274 errorItemId : null ,
193- currentlyViewedFrame : 0 ,
275+ currentViewedFrameIndex : 0 ,
276+ previousViewedFrameIndex : null ,
194277 } ;
195278 } catch ( error ) {
196279 // eslint-disable-next-line no-console
@@ -253,7 +336,8 @@ export const SimulationProvider: React.FC<SimulationProviderProps> = ({
253336 state : newState ,
254337 error : null ,
255338 errorItemId : null ,
256- currentlyViewedFrame : updatedSimulation . currentFrameNumber ,
339+ previousViewedFrameIndex : prev . currentViewedFrameIndex ,
340+ currentViewedFrameIndex : updatedSimulation . currentFrameNumber ,
257341 } ;
258342 } catch ( error ) {
259343 // eslint-disable-next-line no-console
@@ -358,7 +442,8 @@ export const SimulationProvider: React.FC<SimulationProviderProps> = ({
358442 error : null ,
359443 errorItemId : null ,
360444 parameterValues,
361- currentlyViewedFrame : 0 ,
445+ currentViewedFrameIndex : null ,
446+ previousViewedFrameIndex : null ,
362447 // Keep initialMarking when resetting - it's configuration, not simulation state
363448 } ) ) ;
364449 } ,
@@ -384,7 +469,7 @@ export const SimulationProvider: React.FC<SimulationProviderProps> = ({
384469 return { ...prev , state : newState } ;
385470 } ) ,
386471
387- setCurrentlyViewedFrame : ( frameIndex : number ) =>
472+ setCurrentViewedFrame : ( frameIndex : number ) =>
388473 setStateValues ( ( prev ) => {
389474 if ( ! prev . simulation ) {
390475 throw new Error (
@@ -395,12 +480,52 @@ export const SimulationProvider: React.FC<SimulationProviderProps> = ({
395480 const totalFrames = prev . simulation . frames . length ;
396481 const clampedIndex = Math . max ( 0 , Math . min ( frameIndex , totalFrames - 1 ) ) ;
397482
398- return { ...prev , currentlyViewedFrame : clampedIndex } ;
483+ return {
484+ ...prev ,
485+ previousViewedFrameIndex : prev . currentViewedFrameIndex ,
486+ currentViewedFrameIndex : clampedIndex ,
487+ } ;
399488 } ) ,
400489 } ;
401490
491+ // Compute the currently viewed frame state
492+ const currentViewedFrame =
493+ stateValues . currentViewedFrameIndex !== null
494+ ? buildFrameState (
495+ stateValues . simulation ,
496+ stateValues . currentViewedFrameIndex ,
497+ )
498+ : null ;
499+
500+ // Compute the frame diff (comparing current frame with previous frame)
501+ let currentViewedFrameDiff : SimulationFrameStateDiff | null = null ;
502+ if (
503+ currentViewedFrame &&
504+ stateValues . currentViewedFrameIndex !== null &&
505+ stateValues . previousViewedFrameIndex !== null
506+ ) {
507+ const previousFrame = buildFrameState (
508+ stateValues . simulation ,
509+ stateValues . previousViewedFrameIndex ,
510+ ) ;
511+ if ( previousFrame ) {
512+ currentViewedFrameDiff = buildFrameStateDiff (
513+ currentViewedFrame ,
514+ previousFrame ,
515+ ) ;
516+ }
517+ }
518+
402519 const contextValue : SimulationContextValue = {
403- ...stateValues ,
520+ simulation : stateValues . simulation ,
521+ state : stateValues . state ,
522+ error : stateValues . error ,
523+ errorItemId : stateValues . errorItemId ,
524+ parameterValues : stateValues . parameterValues ,
525+ initialMarking : stateValues . initialMarking ,
526+ dt : stateValues . dt ,
527+ currentViewedFrame,
528+ currentViewedFrameDiff,
404529 ...actions ,
405530 } ;
406531
0 commit comments