diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 7db036e17..ff30053c0 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -119,6 +119,11 @@ public: // Tries to cast native interface object to scripting object instance. Returns null if fails. static ScriptingObject* FromInterface(void* interfaceObj, const ScriptingTypeHandle& interfaceType); + template + static ScriptingObject* FromInterface(T* interfaceObj) + { + return FromInterface(interfaceObj, T::TypeInitializer); + } static void* ToInterface(ScriptingObject* obj, const ScriptingTypeHandle& interfaceType); template static T* ToInterface(ScriptingObject* obj) diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index 958bb8d8d..29c7d5eb5 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -65,4 +65,51 @@ TEST_CASE("Scripting") CHECK(arr2[1].Vector == testClass->SimpleStruct.Vector); CHECK(arr2[1].Object == testClass); } + SECTION("Test Interface") + { + // Test native interface implementation + ScriptingTypeHandle type = Scripting::FindScriptingType("FlaxEngine.TestClassNative"); + CHECK(type); + ScriptingObject* object = Scripting::NewObject(type.GetType().ManagedClass); + CHECK(object); + TestClassNative* testClass = (TestClassNative*)object; + int32 methodResult = testClass->TestInterfaceMethod(TEXT("123")); + CHECK(methodResult == 3); + ITestInterface* interface = ScriptingObject::ToInterface(object); + CHECK(interface); + methodResult = interface->TestInterfaceMethod(TEXT("1234")); + CHECK(methodResult == 4); + ScriptingObject* interfaceObject = ScriptingObject::FromInterface(interface); + CHECK(interfaceObject); + CHECK(interfaceObject == object); + + // Test managed interface override + type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged"); + CHECK(type); + object = Scripting::NewObject(type.GetType().ManagedClass); + CHECK(object); + testClass = (TestClassNative*)object; + methodResult = testClass->TestInterfaceMethod(TEXT("123")); + CHECK(methodResult == 6); + interface = ScriptingObject::ToInterface(object); + CHECK(interface); + methodResult = interface->TestInterfaceMethod(TEXT("1234")); + CHECK(methodResult == 8); + interfaceObject = ScriptingObject::FromInterface(interface); + CHECK(interfaceObject); + CHECK(interfaceObject == object); + + // Test managed interface implementation + type = Scripting::FindScriptingType("FlaxEngine.TestInterfaceManaged"); + CHECK(type); + object = Scripting::NewObject(type.GetType().ManagedClass); + CHECK(object); + interface = ScriptingObject::ToInterface(object); + CHECK(interface); + methodResult = interface->TestInterfaceMethod(TEXT("1234")); + CHECK(methodResult == 4); + interfaceObject = ScriptingObject::FromInterface(interface); + CHECK(interfaceObject); + CHECK(interfaceObject == object); + } } diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs index 4f2d9b229..6203de314 100644 --- a/Source/Engine/Tests/TestScripting.cs +++ b/Source/Engine/Tests/TestScripting.cs @@ -69,14 +69,21 @@ namespace FlaxEngine return str.Length + base.TestMethod(str); } + /// + public override int TestInterfaceMethod(string str) + { + // Test C++ base method invocation + return str.Length + base.TestInterfaceMethod(str); + } + private void OnSimpleEvent(int arg1, Float3 arg2, string arg3, ref string arg4, TestStruct[] arg5, ref TestStruct[] arg6) { // Verify that C++ passed proper data to C# via event bindings - if (arg1 == 1 && - arg2 == Float3.One && - arg3 == "1" && - arg4 == "2" && - arg5 != null && arg5.Length == 1 && arg5[0] == SimpleStruct && + if (arg1 == 1 && + arg2 == Float3.One && + arg3 == "1" && + arg4 == "2" && + arg5 != null && arg5.Length == 1 && arg5[0] == SimpleStruct && arg6 != null && arg6.Length == 1 && arg6[0] == SimpleStruct) { // Test passing data back from C# to C++ @@ -94,5 +101,17 @@ namespace FlaxEngine } } } + + /// + /// Test interface in C#. + /// + public class TestInterfaceManaged : Object, ITestInterface + { + /// + public int TestInterfaceMethod(string str) + { + return str.Length; + } + } } #endif diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index 90996de40..c5ba39b10 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -19,8 +19,18 @@ API_STRUCT(NoDefault) struct TestStruct : public ISerializable API_FIELD() ScriptingObject* Object = nullptr; }; +// Test interface. +API_INTERFACE() class ITestInterface +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(ITestInterface); + ~ITestInterface() = default; + + // Test abstract method + API_FUNCTION() virtual int32 TestInterfaceMethod(const String& str) = 0; +}; + // Test class. -API_CLASS() class TestClassNative : public ScriptingObject, public ISerializable +API_CLASS() class TestClassNative : public ScriptingObject, public ISerializable, public ITestInterface { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE(TestClassNative); @@ -40,4 +50,9 @@ public: { return str.Length(); } + + int32 TestInterfaceMethod(const String& str) override + { + return str.Length(); + } };