5
5
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
6
*/
7
7
8
- use godot:: builtin:: Variant ;
9
- use godot:: classes:: { ClassDb , Node , ResourceFormatLoader , ResourceLoader } ;
8
+ use godot:: builtin:: { vslice , Variant } ;
9
+ use godot:: classes:: { ClassDb , Node , RefCounted , ResourceFormatLoader , ResourceLoader } ;
10
10
use godot:: global;
11
+ use godot:: meta:: ToGodot ;
11
12
use godot:: obj:: { Gd , NewAlloc , NewGd } ;
13
+ use godot:: register:: { godot_api, GodotClass } ;
12
14
13
- use crate :: framework:: itest;
15
+ use crate :: framework:: { create_gdscript , itest} ;
14
16
use crate :: object_tests:: object_test:: { user_refc_instance, RefcPayload } ;
15
17
16
18
/*
@@ -142,6 +144,73 @@ fn object_arg_owned_default_params() {
142
144
ResourceLoader :: singleton ( ) . remove_resource_format_loader ( & b) ;
143
145
}
144
146
147
+ // Gd<RefCounted> passed to GDScript should not create unnecessary clones.
148
+ #[ itest]
149
+ fn refcount_asarg_gdscript_calls ( ) {
150
+ let script = create_gdscript (
151
+ r#"
152
+ extends RefCounted
153
+
154
+ func observe_refcount_ptrcall(obj: RefCounted) -> int:
155
+ return obj.get_reference_count()
156
+
157
+ func observe_refcount_varcall(obj) -> int:
158
+ if obj == null:
159
+ return 0
160
+ return obj.get_reference_count()
161
+ "# ,
162
+ ) ;
163
+
164
+ let mut test_instance = RefCounted :: new_gd ( ) ;
165
+ test_instance. set_script ( & script) ;
166
+
167
+ // Already pack into Variant, to have 1 less reference count increment.
168
+ let refc = RefCounted :: new_gd ( ) . to_variant ( ) ;
169
+ assert_eq ! ( refc. call( "get_reference_count" , & [ ] ) , 1 . to_variant( ) ) ;
170
+
171
+ let refcount_typed: i32 = test_instance
172
+ . call ( "observe_refcount_ptrcall" , & [ refc] )
173
+ . to :: < i32 > ( ) ;
174
+
175
+ let refc = RefCounted :: new_gd ( ) . to_variant ( ) ;
176
+ let refcount_untyped = test_instance
177
+ . call ( "observe_refcount_varcall" , & [ refc] )
178
+ . to :: < i32 > ( ) ;
179
+
180
+ let refcount_none = test_instance
181
+ . call ( "observe_refcount_varcall" , vslice ! [ Variant :: nil( ) ] )
182
+ . to :: < i32 > ( ) ;
183
+
184
+ // Both should result in refcount 2: 1 variant in Rust + 1 reference created on GDScript side.
185
+ assert_eq ! ( refcount_typed, 2 , "typed GDScript param (ptrcall)" ) ;
186
+ assert_eq ! ( refcount_untyped, 2 , "untyped GDScript param (varcall)" ) ;
187
+ assert_eq ! ( refcount_none, 0 , "None/null parameter should return 0" ) ;
188
+ }
189
+
190
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
191
+ // Tests with engine APIs + AsArg below, in module.
192
+
193
+ #[ derive( GodotClass ) ]
194
+ #[ class( base = RefCounted , init) ]
195
+ pub struct RefCountAsArgTest ;
196
+
197
+ #[ godot_api]
198
+ impl RefCountAsArgTest {
199
+ #[ func]
200
+ fn accept_option_refcounted ( & self , obj : Option < Gd < RefCounted > > ) -> i32 {
201
+ match obj {
202
+ Some ( gd) => gd. get_reference_count ( ) ,
203
+ None => 0 ,
204
+ }
205
+ }
206
+
207
+ #[ func]
208
+ fn accept_object ( & self , obj : Gd < godot:: classes:: Object > ) -> bool {
209
+ // Just verify we can receive an Object (for upcast testing).
210
+ !obj. get_class ( ) . is_empty ( )
211
+ }
212
+ }
213
+
145
214
// ----------------------------------------------------------------------------------------------------------------------------------------------
146
215
// Helpers
147
216
@@ -164,3 +233,139 @@ where
164
233
165
234
manual2. free ( ) ;
166
235
}
236
+
237
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
238
+ // Tests requiring codegen-full feature
239
+
240
+ #[ cfg( feature = "codegen-full" ) ]
241
+ mod engine_api_tests {
242
+ use std:: cell:: Cell ;
243
+
244
+ use godot:: builtin:: { Rid , Variant } ;
245
+ use godot:: classes:: { base_material_3d, ITexture2D , StandardMaterial3D , Texture2D } ;
246
+ use godot:: meta:: ToGodot ;
247
+ use godot:: obj:: { Base , Gd , NewGd , WithBaseField } ;
248
+ use godot:: register:: { godot_api, GodotClass } ;
249
+
250
+ use crate :: framework:: itest;
251
+
252
+ const ALBEDO : base_material_3d:: TextureParam = base_material_3d:: TextureParam :: ALBEDO ;
253
+
254
+ /// Various internal references are created during `set_texture()`, thus 4. This also matches GDScript code doing the same.
255
+ /// Verified that before this optimization, the refcount was 5.
256
+ const EXPECTED_REFCOUNT : i32 = 4 ;
257
+
258
+ fn verify_refcount < F > ( exp_refcount : i32 , arg_desription : & str , operation : F )
259
+ where
260
+ F : FnOnce ( & mut Gd < StandardMaterial3D > , & Gd < ArgTestTexture > ) ,
261
+ {
262
+ let texture = ArgTestTexture :: new_gd ( ) ;
263
+ let mut material = StandardMaterial3D :: new_gd ( ) ;
264
+
265
+ operation ( & mut material, & texture) ;
266
+
267
+ let captured = texture. bind ( ) . get_captured_refcount ( ) ;
268
+ assert_eq ! ( captured, exp_refcount, "{}" , arg_desription) ;
269
+ }
270
+
271
+ #[ itest]
272
+ fn refcount_asarg_ref ( ) {
273
+ verify_refcount ( EXPECTED_REFCOUNT , "&object" , |mat, tex| {
274
+ // Sanity check: refcount is 1 before call.
275
+ assert_eq ! ( tex. get_reference_count( ) , 1 ) ;
276
+
277
+ mat. set_texture ( ALBEDO , tex) ;
278
+ } ) ;
279
+
280
+ // Derived -> base conversion. 1 extra due to clone().
281
+ verify_refcount ( EXPECTED_REFCOUNT + 1 , "&base_obj" , |mat, tex| {
282
+ let base = tex. clone ( ) . upcast :: < Texture2D > ( ) ;
283
+ mat. set_texture ( ALBEDO , & base) ;
284
+ } ) ;
285
+ }
286
+
287
+ #[ itest]
288
+ fn refcount_asarg_option ( ) {
289
+ verify_refcount ( EXPECTED_REFCOUNT , "Some(&object)" , |mat, tex| {
290
+ mat. set_texture ( ALBEDO , Some ( tex) ) ;
291
+ } ) ;
292
+
293
+ // Derived -> base conversion. 1 extra due to clone().
294
+ verify_refcount ( EXPECTED_REFCOUNT + 1 , "Some(&base_obj)" , |mat, tex| {
295
+ let base = tex. clone ( ) . upcast :: < Texture2D > ( ) ;
296
+ mat. set_texture ( ALBEDO , Some ( & base) ) ;
297
+ } ) ;
298
+
299
+ verify_refcount ( 0 , "None [derived]" , |mat, _tex| {
300
+ mat. set_texture ( ALBEDO , None :: < & Gd < ArgTestTexture > > ) ;
301
+ } ) ;
302
+
303
+ verify_refcount ( 0 , "None [base]" , |mat, _tex| {
304
+ mat. set_texture ( ALBEDO , None :: < & Gd < Texture2D > > ) ;
305
+ } ) ;
306
+ }
307
+
308
+ #[ itest]
309
+ fn refcount_asarg_null_arg ( ) {
310
+ verify_refcount ( 0 , "Gd::null_arg()" , |mat, _tex| {
311
+ mat. set_texture ( ALBEDO , Gd :: null_arg ( ) ) ;
312
+ } ) ;
313
+ }
314
+
315
+ #[ itest]
316
+ fn refcount_asarg_variant ( ) {
317
+ verify_refcount ( EXPECTED_REFCOUNT , "&Variant(tex)" , |mat, tex| {
318
+ mat. set ( "albedo_texture" , & tex. to_variant ( ) ) ;
319
+ } ) ;
320
+
321
+ verify_refcount ( 0 , "&Variant(nil)" , |mat, _tex| {
322
+ mat. set ( "albedo_texture" , & Variant :: nil ( ) ) ;
323
+ } ) ;
324
+ }
325
+
326
+ // ------------------------------------------------------------------------------------------------------------------------------------------
327
+ // Test classes for AsArg testing
328
+
329
+ #[ derive( GodotClass ) ]
330
+ #[ class( base = Texture2D ) ]
331
+ pub struct ArgTestTexture {
332
+ base : Base < Texture2D > ,
333
+ captured_refcount : Cell < i32 > ,
334
+ rid : Rid ,
335
+ }
336
+
337
+ #[ godot_api]
338
+ impl ArgTestTexture {
339
+ fn get_captured_refcount ( & self ) -> i32 {
340
+ self . captured_refcount . get ( )
341
+ }
342
+ }
343
+
344
+ #[ godot_api]
345
+ impl ITexture2D for ArgTestTexture {
346
+ fn init ( base : Base < Texture2D > ) -> Self {
347
+ Self {
348
+ base,
349
+ captured_refcount : Cell :: new ( 0 ) ,
350
+ rid : Rid :: new ( 0 ) ,
351
+ }
352
+ }
353
+
354
+ // Override this method because it's called by StandardMaterial3D::set_texture().
355
+ // We use it as a hook to observe the reference count from the engine side (after passing through AsArg).
356
+ fn get_rid ( & self ) -> Rid {
357
+ self . captured_refcount
358
+ . set ( self . base ( ) . get_reference_count ( ) ) ;
359
+
360
+ self . rid
361
+ }
362
+
363
+ fn get_width ( & self ) -> i32 {
364
+ 1
365
+ }
366
+
367
+ fn get_height ( & self ) -> i32 {
368
+ 1
369
+ }
370
+ }
371
+ }
0 commit comments