@@ -19,7 +19,6 @@ use crate::global::godot_error;
19
19
use crate :: meta:: error:: CallError ;
20
20
use crate :: meta:: CallContext ;
21
21
use crate :: sys;
22
- use std:: collections:: HashMap ;
23
22
use std:: sync:: { atomic, Arc , Mutex } ;
24
23
use sys:: Global ;
25
24
@@ -41,24 +40,57 @@ sys::plugin_registry!(pub __GODOT_PLUGIN_REGISTRY: ClassPlugin);
41
40
42
41
// Note: if this leads to many allocated IDs that are not removed, we could limit to 1 per thread-ID.
43
42
// Would need to check if re-entrant calls with multiple errors per thread are possible.
44
- #[ derive( Default ) ]
45
43
struct CallErrors {
46
- map : HashMap < i32 , CallError > ,
47
- next_id : i32 ,
44
+ ring_buffer : Vec < Option < CallError > > ,
45
+ next_id : u8 ,
46
+ generation : u16 ,
47
+ }
48
+
49
+ impl Default for CallErrors {
50
+ fn default ( ) -> Self {
51
+ Self {
52
+ // [None; N] requires Clone.
53
+ ring_buffer : std:: iter:: repeat_with ( || None )
54
+ . take ( Self :: MAX_ENTRIES as usize )
55
+ . collect ( ) ,
56
+ next_id : 0 ,
57
+ generation : 0 ,
58
+ }
59
+ }
48
60
}
49
61
50
62
impl CallErrors {
63
+ const MAX_ENTRIES : u8 = 32 ;
64
+
51
65
fn insert ( & mut self , err : CallError ) -> i32 {
52
66
let id = self . next_id ;
53
- self . next_id = self . next_id . wrapping_add ( 1 ) ;
54
67
55
- self . map . insert ( id, err) ;
56
- id
68
+ self . next_id = self . next_id . wrapping_add ( 1 ) % Self :: MAX_ENTRIES ;
69
+ if self . next_id == 0 {
70
+ self . generation = self . generation . wrapping_add ( 1 ) ;
71
+ }
72
+
73
+ self . ring_buffer [ id as usize ] = Some ( err) ;
74
+
75
+ ( self . generation as i32 ) << 16 | id as i32
57
76
}
58
77
59
78
// Returns success or failure.
60
79
fn remove ( & mut self , id : i32 ) -> Option < CallError > {
61
- self . map . remove ( & id)
80
+ let generation = ( id >> 16 ) as u16 ;
81
+ let id = id as u8 ;
82
+
83
+ // If id < next_id, the generation must be the current one -- otherwise the one before.
84
+ if id < self . next_id {
85
+ if generation != self . generation {
86
+ return None ;
87
+ }
88
+ } else if generation != self . generation . wrapping_sub ( 1 ) {
89
+ return None ;
90
+ }
91
+
92
+ // Returns Some if there's still an entry, None if it was already removed.
93
+ self . ring_buffer [ id as usize ] . take ( )
62
94
}
63
95
}
64
96
@@ -348,3 +380,54 @@ where
348
380
}
349
381
}
350
382
}
383
+
384
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
385
+
386
+ #[ cfg( test) ]
387
+ mod tests {
388
+ use super :: { CallError , CallErrors } ;
389
+ use crate :: meta:: CallContext ;
390
+
391
+ fn make ( index : usize ) -> CallError {
392
+ let method_name = format ! ( "method_{index}" ) ;
393
+ let ctx = CallContext :: func ( "Class" , & method_name) ;
394
+ CallError :: failed_by_user_panic ( & ctx, "some panic reason" . to_string ( ) )
395
+ }
396
+
397
+ #[ test]
398
+ fn test_call_errors ( ) {
399
+ let mut store = CallErrors :: default ( ) ;
400
+
401
+ let mut id07 = 0 ;
402
+ let mut id13 = 0 ;
403
+ let mut id20 = 0 ;
404
+ for i in 0 ..24 {
405
+ let id = store. insert ( make ( i) ) ;
406
+ match i {
407
+ 7 => id07 = id,
408
+ 13 => id13 = id,
409
+ 20 => id20 = id,
410
+ _ => { }
411
+ }
412
+ }
413
+
414
+ let e = store. remove ( id20) . expect ( "must be present" ) ;
415
+ assert_eq ! ( e. method_name( ) , "method_20" ) ;
416
+
417
+ let e = store. remove ( id20) ;
418
+ assert ! ( e. is_none( ) ) ;
419
+
420
+ for i in 24 ..CallErrors :: MAX_ENTRIES as usize {
421
+ store. insert ( make ( i) ) ;
422
+ }
423
+ for i in 0 ..10 {
424
+ store. insert ( make ( i) ) ;
425
+ }
426
+
427
+ let e = store. remove ( id07) ;
428
+ assert ! ( e. is_none( ) , "generation overwritten" ) ;
429
+
430
+ let e = store. remove ( id13) . expect ( "generation not yet overwritten" ) ;
431
+ assert_eq ! ( e. method_name( ) , "method_13" ) ;
432
+ }
433
+ }
0 commit comments