@@ -101,20 +101,38 @@ public function add_update( string $room, $update ): bool {
101101 public function get_awareness_state ( string $ room , int $ timeout = 30 ): array {
102102 global $ wpdb ;
103103
104- $ cache_key = ' awareness: ' . str_replace ( ' / ' , ' : ' , $ room ) ;
105- $ cached = wp_cache_get ( $ cache_key , ' collaboration ' );
104+ $ cutoff_timestamp = time () - $ timeout ;
105+ $ cutoff_mysql = gmdate ( ' Y-m-d H:i:s ' , $ cutoff_timestamp );
106106
107- if ( false !== $ cached ) {
108- return $ cached ;
109- }
107+ if ( wp_using_ext_object_cache () ) {
108+ $ cache_key = 'awareness:: ' . str_replace ( '/ ' , ': ' , $ room );
109+ $ cached = wp_cache_get ( $ cache_key , 'collaboration ' );
110+
111+ if ( false === $ cached || ! is_string ( $ cached ) ) {
112+ // Room not set/corrupted.
113+ return array ();
114+ }
115+
116+ $ cached = json_decode ( $ cached , true );
117+
118+ // Deterministic ordering.
119+ $ cached_awareness = wp_list_sort ( $ cached , 'client_id ' );
110120
111- $ cutoff = gmdate ( 'Y-m-d H:i:s ' , time () - $ timeout );
121+ // Remove out of date entries.
122+ foreach ( $ cached_awareness as $ index => $ client_awareness ) {
123+ if ( empty ( $ client_awareness ['timestamp ' ] ) || $ client_awareness ['timestamp ' ] < $ cutoff_timestamp ) {
124+ unset( $ cached_awareness [ $ index ] );
125+ }
126+ }
127+
128+ return array_values ( $ cached_awareness );
129+ }
112130
113131 $ rows = $ wpdb ->get_results (
114132 $ wpdb ->prepare (
115133 "SELECT client_id, user_id, data FROM {$ wpdb ->collaboration } WHERE room = %s AND type = 'awareness' AND date_gmt >= %s " ,
116134 $ room ,
117- $ cutoff
135+ $ cutoff_mysql
118136 )
119137 );
120138
@@ -134,8 +152,6 @@ public function get_awareness_state( string $room, int $timeout = 30 ): array {
134152 }
135153 }
136154
137- wp_cache_set ( $ cache_key , $ entries , 'collaboration ' , $ timeout );
138-
139155 return $ entries ;
140156 }
141157
@@ -304,14 +320,50 @@ public function set_awareness_state( string $room, string $client_id, array $sta
304320 return false ;
305321 }
306322
307- $ data = wp_json_encode ( $ state );
323+ wp_recursive_ksort ( $ state );
308324
309325 /*
310326 * Bucket the timestamp to 5-second intervals so most polls
311327 * short-circuit without a database write. Ceil is used instead
312328 * of floor to prevent the awareness timeout from being hit early.
313329 */
314- $ now = gmdate ( 'Y-m-d H:i:s ' , (int ) ceil ( time () / 5 ) * 5 );
330+ $ now_timestamp = (int ) ceil ( time () / 5 ) * 5 ;
331+ $ now_mysql = gmdate ( 'Y-m-d H:i:s ' , $ now_timestamp );
332+
333+ if ( wp_using_ext_object_cache () ) {
334+ $ cache_key = 'awareness:: ' . str_replace ( '/ ' , ': ' , $ room );
335+ $ awareness = $ this ->get_awareness_state ( $ room );
336+
337+ foreach ( $ awareness as $ index => $ client_awareness ) {
338+ if ( $ client_awareness ['client_id ' ] === $ client_id && $ client_awareness ['timestamp ' ] === $ now_timestamp ) {
339+ // Cache already has the current client state, consider update a success (avoids cache thrashing).
340+ return true ;
341+ }
342+
343+ if ( $ client_awareness ['client_id ' ] === $ client_id ) {
344+ // Remove stale cache entry for the current client, if it exists.
345+ unset( $ awareness [ $ index ] );
346+ break ;
347+ }
348+ }
349+
350+ $ client_awareness = array (
351+ 'client_id ' => $ client_id ,
352+ 'state ' => $ state ,
353+ 'user_id ' => $ user_id ,
354+ 'timestamp ' => $ now_timestamp ,
355+ );
356+
357+ $ awareness [] = $ client_awareness ;
358+
359+ // Sort awareness entries by client_id.
360+ $ awareness = wp_list_sort ( $ awareness , 'client_id ' );
361+ wp_cache_set ( $ cache_key , wp_json_encode ( $ awareness ), 'collaboration ' , HOUR_IN_SECONDS );
362+
363+ return true ;
364+ }
365+
366+ $ data = wp_json_encode ( $ state );
315367
316368 /* Check if a row already exists. */
317369 $ exists = $ wpdb ->get_row (
@@ -322,7 +374,7 @@ public function set_awareness_state( string $room, string $client_id, array $sta
322374 )
323375 );
324376
325- if ( $ exists && $ exists ->date_gmt === $ now ) {
377+ if ( $ exists && $ exists ->date_gmt === $ now_mysql ) {
326378 // Row already has the current date, consider update a success.
327379 return true ;
328380 }
@@ -333,7 +385,7 @@ public function set_awareness_state( string $room, string $client_id, array $sta
333385 array (
334386 'user_id ' => $ user_id ,
335387 'data ' => $ data ,
336- 'date_gmt ' => $ now ,
388+ 'date_gmt ' => $ now_mysql ,
337389 ),
338390 array ( 'id ' => $ exists ->id )
339391 );
@@ -346,7 +398,7 @@ public function set_awareness_state( string $room, string $client_id, array $sta
346398 'client_id ' => $ client_id ,
347399 'user_id ' => $ user_id ,
348400 'data ' => $ data ,
349- 'date_gmt ' => $ now ,
401+ 'date_gmt ' => $ now_mysql ,
350402 )
351403 );
352404 }
@@ -355,38 +407,6 @@ public function set_awareness_state( string $room, string $client_id, array $sta
355407 return false ;
356408 }
357409
358- /*
359- * Update the cached entries in-place so the next reader in this
360- * room gets a cache hit with fresh data. If the cache is cold,
361- * skip — the next get_awareness_state() call will prime it.
362- */
363- $ cache_key = 'awareness: ' . str_replace ( '/ ' , ': ' , $ room );
364- $ cached = wp_cache_get ( $ cache_key , 'collaboration ' );
365-
366- if ( false !== $ cached ) {
367- $ normalized_state = json_decode ( $ data , true );
368- $ found = false ;
369-
370- foreach ( $ cached as $ i => $ entry ) {
371- if ( $ client_id === $ entry ['client_id ' ] ) {
372- $ cached [ $ i ]['state ' ] = $ normalized_state ;
373- $ cached [ $ i ]['user_id ' ] = $ user_id ;
374- $ found = true ;
375- break ;
376- }
377- }
378-
379- if ( ! $ found ) {
380- $ cached [] = array (
381- 'client_id ' => $ client_id ,
382- 'state ' => $ normalized_state ,
383- 'user_id ' => $ user_id ,
384- );
385- }
386-
387- wp_cache_set ( $ cache_key , $ cached , 'collaboration ' , 30 );
388- }
389-
390410 return true ;
391411 }
392412}
0 commit comments