@@ -17,6 +17,76 @@ const serializeProxyObjects = (proxy: any) => {
1717 return proxy == undefined ? undefined : JSON . parse ( JSON . stringify ( proxy ) ) ;
1818} ;
1919
20+ // Type for tracking write operations per context+key
21+ type TimeoutId = ReturnType < typeof setTimeout > ;
22+ type WriteTracking = {
23+ pendingTimeout : TimeoutId | null ;
24+ pendingCallback : ( ( ) => void ) | null ; // Store the callback to execute when timeout fires
25+ writeCount : number ;
26+ lastResetTime : number ;
27+ } ;
28+
29+ // Map to store write tracking by context and key
30+ const writeTrackers = new Map < any , Map < string , WriteTracking > > ( ) ;
31+
32+ // Configuration
33+ const MAX_IMMEDIATE_WRITES_PER_SECOND = 20 ; // Allow 20 immediate writes per second
34+ const THROTTLED_WRITE_INTERVAL_MS = 100 ; // 0.1s interval after threshold
35+
36+ // Throttle function that handles write rate limiting
37+ function throttle ( context : any , key : string , callback : ( ) => void ) : void {
38+ // Get or create context map for this specific context
39+ if ( ! writeTrackers . has ( context ) ) {
40+ writeTrackers . set ( context , new Map ( ) ) ;
41+ }
42+ const contextMap = writeTrackers . get ( context ) ! ;
43+
44+ // Get or initialize tracking info for this key
45+ if ( ! contextMap . has ( key ) ) {
46+ contextMap . set ( key , {
47+ pendingTimeout : null ,
48+ pendingCallback : null ,
49+ writeCount : 0 ,
50+ lastResetTime : Date . now ( ) ,
51+ } ) ;
52+ }
53+
54+ const tracking = contextMap . get ( key ) ! ;
55+ const now = Date . now ( ) ;
56+
57+ // Reset counter if a second has passed
58+ if ( now - tracking . lastResetTime > 1000 ) {
59+ tracking . writeCount = 0 ;
60+ tracking . lastResetTime = now ;
61+ if ( tracking . pendingTimeout ) {
62+ clearTimeout ( tracking . pendingTimeout ) ;
63+ tracking . pendingTimeout = null ;
64+ }
65+ }
66+
67+ // If we're under the threshold, process immediately
68+ if ( tracking . writeCount < MAX_IMMEDIATE_WRITES_PER_SECOND ) {
69+ tracking . writeCount ++ ;
70+ // Execute callback immediately
71+ callback ( ) ;
72+ } else {
73+ // Update the callback to be executed when the timeout fires
74+ tracking . pendingCallback = callback ;
75+
76+ // Only set a new timeout if there isn't one already
77+ if ( ! tracking . pendingTimeout ) {
78+ tracking . pendingTimeout = setTimeout ( ( ) => {
79+ // Execute the latest callback
80+ tracking . pendingCallback ?.( ) ;
81+
82+ // Clear the timeout reference
83+ tracking . pendingTimeout = null ;
84+ tracking . pendingCallback = null ;
85+ } , THROTTLED_WRITE_INTERVAL_MS ) ;
86+ }
87+ }
88+ }
89+
2090export const setupIframe = ( ) =>
2191 setIframeContextHandler ( {
2292 read ( context : any , key : string ) : any {
@@ -25,23 +95,30 @@ export const setupIframe = () =>
2595 return serialized ;
2696 } ,
2797 write ( context : any , key : string , value : any ) {
28- if ( isCell ( context ) ) {
29- context . key ( key ) . setRaw ( value ) ;
30- } else {
31- context [ key ] = value ;
32- }
98+ throttle ( context , key , ( ) => {
99+ if ( isCell ( context ) ) {
100+ context . key ( key ) . setRaw ( value ) ;
101+ } else {
102+ context [ key ] = value ;
103+ }
104+ } ) ;
33105 } ,
34106 subscribe (
35107 context : any ,
36108 key : string ,
37109 callback : ( key : string , value : any ) => void ,
38110 ) : any {
111+ let previousValue : any ;
112+
39113 const action : Action = ( log : ReactivityLog ) => {
40114 const data = isCell ( context )
41115 ? context . withLog ( log ) . key ( key ) . get ( )
42116 : context ?. [ key ] ;
43117 const serialized = serializeProxyObjects ( data ) ;
44- callback ( key , serialized ) ;
118+ if ( serialized !== previousValue ) {
119+ previousValue = serialized ;
120+ callback ( key , serialized ) ;
121+ }
45122 } ;
46123
47124 addAction ( action ) ;
0 commit comments