Skip to content

Commit

Permalink
Provide safe way for implementing IScriptExtension::instance_has
Browse files Browse the repository at this point in the history
  • Loading branch information
TitanNano committed Jan 18, 2025
1 parent 7461251 commit a24ca47
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 22 deletions.
2 changes: 2 additions & 0 deletions godot-codegen/src/special_cases/codegen_special_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ const SELECTED_CLASSES: &[&str] = &[
"SceneTreeTimer",
"Script",
"ScriptExtension",
"ScriptNameCasing",
"ScriptLanguage",
"ScriptLanguageExtension",
"Sprite2D",
"SpriteFrames",
"TextServer",
Expand Down
31 changes: 29 additions & 2 deletions godot-core/src/obj/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ use godot_cell::panicking::{GdCell, MutGuard, RefGuard};
use godot_cell::blocking::{GdCell, MutGuard, RefGuard};

use crate::builtin::{GString, StringName, Variant, VariantType};
use crate::classes::{Script, ScriptLanguage};
use crate::classes::{Object, Script, ScriptLanguage};
use crate::meta::{MethodInfo, PropertyInfo};
use crate::obj::{Base, Gd, GodotClass};
use crate::obj::{Base, Gd, GodotClass, Inherits};
use crate::sys;

#[cfg(before_api = "4.3")]
Expand Down Expand Up @@ -337,6 +337,33 @@ pub unsafe fn create_script_instance<T: ScriptInstance>(
}
}

/// Checks if a script instance exists for a given object.
///
/// The engine does not expose the [`ScriptLanguage`] of a [`Script`] (as of 4.3) and it's therefore necessary to pass the expected
/// language as well.
///
/// Use this function to implement [`IScriptExtension::instance_has`](crate::classes::IScriptExtension::instance_has).
#[cfg(since_api = "4.2")]
pub fn script_instance_exists<O, S, L>(object: &Gd<O>, script: &Gd<S>, language: &Gd<L>) -> bool
where
O: Inherits<Object>,
S: Inherits<Script>,
L: Inherits<ScriptLanguage>,
{
let get_instance_fn = sys::interface_fn!(object_get_script_instance);

let object_script_variant = object.upcast_ref().get_script();

if object_script_variant.is_nil() {
return false;
}

let object_script: Gd<Script> = object_script_variant.to();
let instance = unsafe { get_instance_fn(object.obj_sys(), language.obj_sys()) };

!instance.is_null() && object_script == script.clone().upcast()
}

/// Mutable/exclusive reference guard for a `T` where `T` implements [`ScriptInstance`].
///
/// 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.
Expand Down
56 changes: 43 additions & 13 deletions itest/godot/ScriptInstanceTests.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,103 +5,133 @@

extends TestSuite

func create_script_instance() -> RefCounted:
var script = TestScript.new()
func create_script_instance() -> Array:
var language: TestScriptLanguage = TestScriptLanguage.new()
var script: TestScript = language.new_script()
var script_owner = RefCounted.new()

script_owner.script = script

return script_owner
return [script_owner, language]


func test_script_instance_get_property():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]

var value: int = object.script_property_a

assert_eq(value, 10)
language.free()


func test_script_instance_set_property():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]

assert_eq(object.script_property_b, false)

object.script_property_b = true

assert_eq(object.script_property_b, true)
language.free()


func test_script_instance_call():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]

var arg_a = "test string"
var arg_b = 5

var result = object.script_method_a(arg_a, arg_b)

assert_eq(result, "{0}{1}".format([arg_a, arg_b]))
language.free()


func test_script_instance_property_list():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]

var list = object.get_property_list()

assert_eq(list[-1]["name"], "script_property_a");
assert_eq(list[-1]["type"], Variant.Type.TYPE_INT)
language.free()


func test_script_instance_method_list():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]

var list = object.get_method_list()

assert_eq(list[-1]["name"], "script_method_a")
assert_eq(list[-1]["args"][0]["type"], Variant.Type.TYPE_STRING)
assert_eq(list[-1]["args"][1]["type"], Variant.Type.TYPE_INT)
language.free()


func test_script_instance_has_method():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]

assert(object.has_method("script_method_a"));
assert(!object.has_method("script_method_z"));
language.free()


func test_script_instance_to_string():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]

assert_eq(object.to_string(), "script instance to string")
language.free()


func test_script_instance_mut_call():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]
var before = object.script_property_b

var result = object.script_method_toggle_property_b()

assert(result)
assert_eq(object.script_property_b, !before)
language.free()


func test_script_instance_re_entering_call():
var object = create_script_instance()
var tuple := create_script_instance()
var object: RefCounted = tuple[0]
var language: TestScriptLanguage = tuple[1]
var before = object.script_property_b

var result = object.script_method_re_entering()

assert(result)
assert_eq(object.script_property_b, !before)
language.free()


func test_object_script_instance():
var object = Node.new()
var script = TestScript.new()
var language: TestScriptLanguage = TestScriptLanguage.new()
var script: TestScript = language.new_script()

object.script = script

var result = object.script_method_re_entering()

assert(result)
object.free()
language.free()
Loading

0 comments on commit a24ca47

Please sign in to comment.