Skip to content

Commit 7d0de74

Browse files
committed
Provide safe way for implementing IScriptExtension::instance_has
1 parent 7461251 commit 7d0de74

File tree

4 files changed

+172
-11
lines changed

4 files changed

+172
-11
lines changed

godot-codegen/src/special_cases/codegen_special_cases.rs

+2
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ const SELECTED_CLASSES: &[&str] = &[
173173
"SceneTreeTimer",
174174
"Script",
175175
"ScriptExtension",
176+
"ScriptNameCasing",
176177
"ScriptLanguage",
178+
"ScriptLanguageExtension",
177179
"Sprite2D",
178180
"SpriteFrames",
179181
"TextServer",

godot-core/src/obj/script.rs

+29-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ use godot_cell::panicking::{GdCell, MutGuard, RefGuard};
2525
use godot_cell::blocking::{GdCell, MutGuard, RefGuard};
2626

2727
use crate::builtin::{GString, StringName, Variant, VariantType};
28-
use crate::classes::{Script, ScriptLanguage};
28+
use crate::classes::{Object, Script, ScriptLanguage};
2929
use crate::meta::{MethodInfo, PropertyInfo};
30-
use crate::obj::{Base, Gd, GodotClass};
30+
use crate::obj::{Base, Gd, GodotClass, Inherits};
3131
use crate::sys;
3232

3333
#[cfg(before_api = "4.3")]
@@ -337,6 +337,33 @@ pub unsafe fn create_script_instance<T: ScriptInstance>(
337337
}
338338
}
339339

340+
/// Checks if a script instance exists for a given object.
341+
///
342+
/// The engine does not expose the [`ScriptLanguage`] of a [`Script`] (as of 4.3) and it's therefore necessary to pass the expected
343+
/// language as well.
344+
///
345+
/// Use this function to implement [`IScriptExtension::instance_has`](crate::classes::IScriptExtension::instance_has).
346+
#[cfg(since_api = "4.2")]
347+
pub fn script_instance_exists<O, S, L>(object: &Gd<O>, script: &Gd<S>, language: &Gd<L>) -> bool
348+
where
349+
O: Inherits<Object>,
350+
S: Inherits<Script>,
351+
L: Inherits<ScriptLanguage>,
352+
{
353+
let get_instance_fn = sys::interface_fn!(object_get_script_instance);
354+
355+
let object_script_variant = object.upcast_ref().get_script();
356+
357+
if object_script_variant.is_nil() {
358+
return false;
359+
}
360+
361+
let object_script: Gd<Script> = object_script_variant.to();
362+
let instance = unsafe { get_instance_fn(object.obj_sys(), language.obj_sys()) };
363+
364+
!instance.is_null() && object_script == script.clone().upcast()
365+
}
366+
340367
/// Mutable/exclusive reference guard for a `T` where `T` implements [`ScriptInstance`].
341368
///
342369
/// This can be used to access the base object of a [`ScriptInstance`], which in turn can be used to make reentrant calls to engine APIs.

itest/godot/ScriptInstanceTests.gd

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
extends TestSuite
77

88
func create_script_instance() -> RefCounted:
9-
var script = TestScript.new()
9+
var language: TestScriptLanguage = TestScriptLanguage.new()
10+
var script: TestScript = language.new_script()
1011
var script_owner = RefCounted.new()
1112

1213
script_owner.script = script
@@ -97,11 +98,13 @@ func test_script_instance_re_entering_call():
9798

9899
func test_object_script_instance():
99100
var object = Node.new()
100-
var script = TestScript.new()
101+
var language: TestScriptLanguage = TestScriptLanguage.new()
102+
var script: TestScript = language.new_script()
101103

102104
object.script = script
103105

104106
var result = object.script_method_re_entering()
105107

106108
assert(result)
107109
object.free()
110+
language.free()

itest/rust/src/builtin_tests/script/script_instance_tests.rs

+136-7
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,37 @@
88
use std::ffi::c_void;
99

1010
use godot::builtin::{Array, Dictionary, GString, StringName, Variant, VariantType};
11-
use godot::classes::{IScriptExtension, Object, Script, ScriptExtension, ScriptLanguage};
11+
use godot::classes::script_language::ScriptNameCasing;
12+
use godot::classes::{
13+
IScriptExtension, IScriptLanguageExtension, Object, Script, ScriptExtension, ScriptLanguage,
14+
ScriptLanguageExtension,
15+
};
1216
use godot::global::{Error, MethodFlags};
1317
use godot::meta::{ClassName, FromGodot, MethodInfo, PropertyInfo, ToGodot};
1418
use godot::obj::script::{create_script_instance, ScriptInstance, SiMut};
15-
use godot::obj::{Base, Gd, WithBaseField};
19+
use godot::obj::{Base, Gd, NewAlloc, WithBaseField};
1620
use godot::register::{godot_api, GodotClass};
1721
use godot::sys;
1822

23+
use crate::framework::itest;
24+
1925
#[derive(GodotClass)]
20-
#[class(base = ScriptExtension, init, tool)]
26+
#[class(base = ScriptExtension, no_init, tool)]
2127
struct TestScript {
28+
language: Gd<TestScriptLanguage>,
2229
base: Base<ScriptExtension>,
2330
}
2431

32+
impl TestScript {
33+
fn new(language: Gd<TestScriptLanguage>) -> Gd<Self> {
34+
Gd::from_init_fn(|base| Self { language, base })
35+
}
36+
}
37+
2538
#[rustfmt::skip]
2639
#[godot_api]
2740
impl IScriptExtension for TestScript {
41+
2842
fn can_instantiate(&self) -> bool {
2943
true
3044
}
@@ -33,6 +47,10 @@ impl IScriptExtension for TestScript {
3347
create_script_instance(TestScriptInstance::new(self.to_gd().upcast()), for_object)
3448
}
3549

50+
fn get_language(&self) -> Option<Gd<ScriptLanguage>> {
51+
Some(self.language.clone().upcast())
52+
}
53+
3654
fn editor_can_reload_from_file(&mut self) -> bool { unreachable!() }
3755
fn get_base_script(&self) -> Option<Gd<Script>> { unreachable!() }
3856
fn get_global_name(&self) -> StringName { unreachable!() }
@@ -51,7 +69,6 @@ impl IScriptExtension for TestScript {
5169
fn get_method_info(&self, _method: StringName) -> Dictionary { unreachable!() }
5270
fn is_tool(&self) -> bool { unreachable!() }
5371
fn is_valid(&self) -> bool { unreachable!() }
54-
fn get_language(&self) -> Option<Gd<ScriptLanguage>> { unreachable!() }
5572
fn has_script_signal(&self, _signall: StringName) -> bool { unreachable!() }
5673
fn get_script_signal_list(&self) -> Array<Dictionary> { unreachable!() }
5774
fn has_property_default_value(&self, _property: StringName) -> bool { unreachable!() }
@@ -75,12 +92,18 @@ struct TestScriptInstance {
7592
prop_list: Vec<PropertyInfo>,
7693
method_list: Vec<MethodInfo>,
7794
script: Gd<Script>,
95+
script_language: Gd<ScriptLanguage>,
7896
}
7997

8098
impl TestScriptInstance {
81-
fn new(script: Gd<Script>) -> Self {
99+
fn new(script: Gd<TestScript>) -> Self {
82100
Self {
83-
script,
101+
script_language: {
102+
let s = script.bind();
103+
104+
s.get_language().unwrap()
105+
},
106+
script: script.upcast(),
84107
script_property_b: false,
85108
prop_list: vec![PropertyInfo::new_var::<i64>("script_property_a")],
86109

@@ -200,7 +223,7 @@ impl ScriptInstance for TestScriptInstance {
200223
}
201224

202225
fn get_language(&self) -> Gd<ScriptLanguage> {
203-
panic!("language is not implemented")
226+
self.script_language.clone()
204227
}
205228

206229
fn on_refcount_decremented(&self) -> bool {
@@ -222,3 +245,109 @@ impl ScriptInstance for TestScriptInstance {
222245
None
223246
}
224247
}
248+
249+
#[derive(GodotClass)]
250+
#[class(base = ScriptLanguageExtension, tool, init)]
251+
struct TestScriptLanguage {
252+
base: Base<ScriptLanguageExtension>,
253+
}
254+
255+
#[godot_api]
256+
impl TestScriptLanguage {
257+
#[func]
258+
fn new_script(&self) -> Gd<TestScript> {
259+
TestScript::new(self.base().to_godot().cast())
260+
}
261+
}
262+
263+
#[godot_api]
264+
#[rustfmt::skip]
265+
impl IScriptLanguageExtension for TestScriptLanguage {
266+
fn get_name(&self) -> GString { unreachable!() }
267+
fn init_ext(&mut self) { unreachable!() }
268+
fn get_type(&self) -> GString { unreachable!() }
269+
fn get_extension(&self) -> GString { unreachable!() }
270+
fn finish(&mut self) { unreachable!() }
271+
fn get_reserved_words(&self) -> godot::prelude::PackedStringArray { unreachable!() }
272+
fn is_control_flow_keyword(&self, _keyword: GString) -> bool { unreachable!() }
273+
fn get_comment_delimiters(&self) -> godot::prelude::PackedStringArray { unreachable!() }
274+
fn get_string_delimiters(&self) -> godot::prelude::PackedStringArray { unreachable!() }
275+
fn make_template(&self, _template: GString, _class_name: GString, _base_class_name: GString) -> Option<Gd<Script>> { unreachable!() }
276+
fn get_built_in_templates(&self, _object: StringName) -> Array<Dictionary> { unreachable!() }
277+
fn is_using_templates(&mut self) -> bool { unreachable!() }
278+
fn validate(&self, _script: GString, _path: GString, _validate_functions: bool, _validate_errors: bool, _validate_warnings: bool, _validate_safe_lines: bool) -> Dictionary { unreachable!() }
279+
fn validate_path(&self, _path: GString) -> GString { unreachable!() }
280+
fn create_script(&self) -> Option<Gd<Object>> { unreachable!() }
281+
fn has_named_classes(&self) -> bool { unreachable!() }
282+
fn supports_builtin_mode(&self) -> bool { unreachable!() }
283+
fn supports_documentation(&self) -> bool { unreachable!() }
284+
fn can_inherit_from_file(&self) -> bool { unreachable!() }
285+
fn find_function(&self, _class_name: GString, _function_namee: GString) -> i32 { unreachable!() }
286+
fn make_function(&self, _class_name: GString, _function_name: GString, _function_args: godot::prelude::PackedStringArray) -> GString { unreachable!() }
287+
fn open_in_external_editor(&mut self, _script: Option<Gd<Script>>, _line: i32, _column: i32) -> godot::global::Error { unreachable!() }
288+
fn overrides_external_editor(&mut self) -> bool { unreachable!() }
289+
fn complete_code(&self, _code: GString,_pathh: GString, _ownerer: Option<Gd<Object>>) -> Dictionary { unreachable!() }
290+
fn lookup_code(&self, _code: GString, _symbol: GString, _path: GString, _owner: Option<Gd<Object>>) -> Dictionary { unreachable!() }
291+
fn auto_indent_code(&self, _code: GString, _from_linee: i32, _to_line: i32) -> GString { unreachable!() }
292+
fn add_global_constant(&mut self, _name: StringName,_valuee: Variant) { unreachable!() }
293+
fn add_named_global_constant(&mut self, _name: StringName,_valuee: Variant) { unreachable!() }
294+
fn remove_named_global_constant(&mut self, _name: StringName) { unreachable!() }
295+
fn thread_enter(&mut self) { unreachable!() }
296+
fn thread_exit(&mut self) { unreachable!() }
297+
fn debug_get_error(&self) -> GString { unreachable!() }
298+
fn debug_get_stack_level_count(&self) -> i32 { unreachable!() }
299+
fn debug_get_stack_level_line(&self, _level: i32) -> i32 { unreachable!() }
300+
fn debug_get_stack_level_function(&self, _level: i32) -> GString { unreachable!() }
301+
fn debug_get_stack_level_locals(&mut self, _level: i32, _max_subitems: i32, _max_depth: i32) -> Dictionary { unreachable!() }
302+
fn debug_get_stack_level_members(&mut self, _level: i32, _max_subitems: i32, _max_depth: i32) -> Dictionary { unreachable!() }
303+
unsafe fn debug_get_stack_level_instance(&mut self, _level: i32) -> *mut c_void { unreachable!() }
304+
fn debug_get_globals(&mut self, _max_subitems: i32,_max_depthh: i32) -> Dictionary { unreachable!() }
305+
fn debug_parse_stack_level_expression(&mut self, _level: i32, _expression: GString, _max_subitems: i32, _max_depth: i32) -> GString { unreachable!() }
306+
fn debug_get_current_stack_info(&mut self) -> Array<Dictionary> { unreachable!() }
307+
fn reload_all_scripts(&mut self) { unreachable!() }
308+
fn reload_tool_script(&mut self, _script: Option<Gd<Script>>,_soft_reloadd: bool) { unreachable!() }
309+
fn get_recognized_extensions(&self) -> godot::prelude::PackedStringArray { unreachable!() }
310+
fn get_public_functions(&self) -> Array<Dictionary> { unreachable!() }
311+
fn get_public_constants(&self) -> Dictionary { unreachable!() }
312+
fn get_public_annotations(&self) -> Array<Dictionary> { unreachable!() }
313+
fn profiling_start(&mut self) { unreachable!() }
314+
fn profiling_stop(&mut self) { unreachable!() }
315+
unsafe fn profiling_get_accumulated_data(&mut self, _info_array: *mut godot::classes::native::ScriptLanguageExtensionProfilingInfo, _info_max: i32) -> i32 { unreachable!() }
316+
unsafe fn profiling_get_frame_data(&mut self, _info_array: *mut godot::classes::native::ScriptLanguageExtensionProfilingInfo, _info_max: i32) -> i32 { unreachable!() }
317+
fn frame(&mut self) { unreachable!() }
318+
fn handles_global_class_type(&self, _type_: GString) -> bool { unreachable!() }
319+
fn get_global_class_name(&self, _path: GString) -> Dictionary { unreachable!() }
320+
#[cfg(since_api = "4.3")]
321+
fn profiling_set_save_native_calls(&mut self, _enable: bool) { unreachable!() }
322+
#[cfg(since_api = "4.3")]
323+
fn debug_get_stack_level_source(&self, _level: i32) -> GString { unreachable!() }
324+
#[cfg(since_api = "4.3")]
325+
fn can_make_function(&self) -> bool { unreachable!() }
326+
#[cfg(since_api = "4.3")]
327+
fn preferred_file_name_casing(&self) -> ScriptNameCasing { unreachable!() }
328+
#[cfg(since_api = "4.4")]
329+
fn reload_scripts(&mut self, _scripts: Array<Variant>, _soft: bool) { unreachable!() }
330+
}
331+
332+
// -------- Test Cases --------
333+
334+
/// Test that [`script_instance_exists`] returns true if a instance of a script exists for the given object.
335+
#[itest]
336+
fn script_instance_exists() {
337+
let language = TestScriptLanguage::new_alloc();
338+
let script = TestScript::new(language.clone());
339+
let mut object = Object::new_alloc();
340+
341+
object.set_script(&script.to_variant());
342+
343+
let instance_exists = godot::obj::script::script_instance_exists(&object, &script, &language);
344+
assert!(instance_exists);
345+
346+
object.set_script(&Variant::nil());
347+
348+
let instance_exists = godot::obj::script::script_instance_exists(&object, &script, &language);
349+
assert!(!instance_exists);
350+
351+
object.free();
352+
language.free();
353+
}

0 commit comments

Comments
 (0)