From 0d2f90625fc14e39e0e9133f923465b193020837 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 08:41:09 -0300 Subject: [PATCH 01/20] WIP Add GDObject Objective-C class for wrapping compatible Godot objects --- src/GDObject.hpp | 47 ++++++++++++++++++++++++++ src/GDObject.mm | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 src/GDObject.hpp create mode 100644 src/GDObject.mm diff --git a/src/GDObject.hpp b/src/GDObject.hpp new file mode 100644 index 0000000..042ae97 --- /dev/null +++ b/src/GDObject.hpp @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2023 Gil Barbosa Reis. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __GDOOBJECT_HPP__ +#define __GDOOBJECT_HPP__ + +#include + +#include + +using namespace godot; + +/** + * Objective-C object that wraps Godot objects. + * + * Messaging this object will forward the calls to the Godot object, + * removing the last ":" and replacing the other ":" characters for "_". + */ +@interface GDObject : NSObject + ++ (instancetype)objectWithObject:(Object *)object; +- (instancetype)initWithObject:(Object *)object; + ++ (BOOL)isCompatibleObject:(Object *)object; ++ (String)godotNameForSelector:(SEL)selector; + +@end + +#endif // __GDOOBJECT_HPP__ diff --git a/src/GDObject.mm b/src/GDObject.mm new file mode 100644 index 0000000..55756c4 --- /dev/null +++ b/src/GDObject.mm @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2023 Gil Barbosa Reis. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "GDObject.hpp" + +#include + +@implementation GDObject { + Object *_obj; +} + ++ (instancetype)objectWithObject:(Object *)object { + return [[[self alloc] initWithObject:object] autorelease]; +} + +- (instancetype)initWithObject:(Object *)object { + if (self = [super init]) { + _obj = object; + if (RefCounted *ref = Object::cast_to(object)) { + ref->reference(); + } + } + return self; +} + +- (void)dealloc { + if (RefCounted *ref = Object::cast_to(_obj)) { + ref->unreference(); + } + [super dealloc]; +} + +- (instancetype)copyWithZone:(NSZone *)zone { + return [self retain]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { + // TODO + return [super methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation { + String methodName = [GDObject godotNameForSelector:anInvocation.selector]; + // TODO + [super forwardInvocation:anInvocation]; +} + ++ (BOOL)isCompatibleObject:(Object *)object { + if (object) { + return object->has_method("methodSignatureForSelector"); + } + else { + return NO; + } +} + ++ (String)godotNameForSelector:(SEL)selector { + if (selector) { + return String(sel_getName(selector)) + .trim_suffix(":") + .replace(":", "_"); + } + else { + return ""; + } +} + +@end From 7106e70d072083016bbc911877e7928555280d4a Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 09:17:24 -0300 Subject: [PATCH 02/20] Add more marshalling functions to support NSInvocation --- src/objc_marshalling.hpp | 2 ++ src/objc_marshalling.mm | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/objc_marshalling.hpp b/src/objc_marshalling.hpp index 2910b75..ef664fe 100644 --- a/src/objc_marshalling.hpp +++ b/src/objc_marshalling.hpp @@ -31,7 +31,9 @@ namespace objcgdextension { Variant get_variant(const char *objc_type, const void *buffer); Variant get_result_variant(NSInvocation *invocation); +Variant get_argument_variant(NSInvocation *invocation, NSUInteger index); bool set_variant(const char *objc_type, void *buffer, const Variant& value, Array& string_holder); +bool set_result_variant(NSInvocation *invocation, const Variant& value, Array& string_holder); } diff --git a/src/objc_marshalling.mm b/src/objc_marshalling.mm index e2822d0..d6285fe 100644 --- a/src/objc_marshalling.mm +++ b/src/objc_marshalling.mm @@ -79,6 +79,11 @@ inline BOOL is_method_encoding(char c) { return type; } +struct InvocationArgument { + NSInvocation *invocation; + NSUInteger index; +}; + template T deref_as(const void *buffer) { return *static_cast(buffer); @@ -91,6 +96,13 @@ T deref_as(NSInvocation *invocation) { return value; } +template +T deref_as(InvocationArgument& arg) { + T value; + [arg.invocation getArgument:&value atIndex:arg.index]; + return value; +} + template Variant get_variant(const char *objc_type, TDeref buffer) { objc_type = skip_method_encodings(objc_type); @@ -167,13 +179,25 @@ Variant get_result_variant(NSInvocation *invocation) { return get_variant(invocation.methodSignature.methodReturnType, invocation); } +Variant get_argument_variant(NSInvocation *invocation, NSUInteger index) { + InvocationArgument arg = { invocation, index }; + return get_variant([invocation.methodSignature getArgumentTypeAtIndex:index], arg); +} + template bool set_value(void *buffer, const T& value) { *static_cast(buffer) = value; return true; } -bool set_variant(const char *objc_type, void *buffer, const Variant& value, Array& string_holder) { +template +bool set_value(NSInvocation *invocation, const T& value) { + [invocation setReturnValue:(void *) &value]; + return true; +} + +template +bool set_variant(const char *objc_type, TDeref buffer, const Variant& value, Array& string_holder) { objc_type = skip_method_encodings(objc_type); switch (objc_type[0]) { case 'B': @@ -250,4 +274,12 @@ bool set_variant(const char *objc_type, void *buffer, const Variant& value, Arra } } +bool set_variant(const char *objc_type, void *buffer, const Variant& value, Array& string_holder) { + return set_variant(objc_type, buffer, value, string_holder); +} + +bool set_result_variant(NSInvocation *invocation, const Variant& value, Array& string_holder) { + return set_variant(invocation.methodSignature.methodReturnType, invocation, value, string_holder); +} + } From bea0f46fb10ff311790515ff4b32c90ba8b56b97 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 09:42:23 -0300 Subject: [PATCH 03/20] Implement methodSignatureForSelector: and forwardInvocation: in GDObject --- src/GDObject.mm | 34 +++++++++++++++++++-- src/NSMethodSignature+ArgumentsFromData.hpp | 1 + src/NSMethodSignature+ArgumentsFromData.mm | 8 +++++ src/objc_conversions.hpp | 1 + src/objc_conversions.mm | 4 +++ 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/GDObject.mm b/src/GDObject.mm index 55756c4..8d0232b 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -21,8 +21,15 @@ */ #include "GDObject.hpp" +#import "NSMethodSignature+ArgumentsFromData.hpp" +#import "objc_conversions.hpp" +#import "objc_marshalling.hpp" + +#include #include +using namespace objcgdextension; + @implementation GDObject { Object *_obj; } @@ -53,13 +60,36 @@ - (instancetype)copyWithZone:(NSZone *)zone { } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { - // TODO + @try { + if (_obj) { + String methodSignature = _obj->call("methodSignatureForSelector", to_string(aSelector)); + if (!methodSignature.is_empty()) { + return [NSMethodSignature signatureWithObjCTypes:methodSignature.ascii().get_data()]; + } + } + } + @catch (NSException *ex) { + ERR_PRINT(ex.description.UTF8String); + } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { String methodName = [GDObject godotNameForSelector:anInvocation.selector]; - // TODO + if (_obj) { + if (_obj->has_method(methodName)) { + Array args = [anInvocation.methodSignature arrayFromInvocationArguments:anInvocation]; + Variant result = _obj->callv(methodName, args); + set_result_variant(anInvocation, result, args); + return; + } + else if (anInvocation.methodSignature.numberOfArguments == 0) { + Variant property = _obj->get(methodName); + Array string_holder; + set_result_variant(anInvocation, property, string_holder); + return; + } + } [super forwardInvocation:anInvocation]; } diff --git a/src/NSMethodSignature+ArgumentsFromData.hpp b/src/NSMethodSignature+ArgumentsFromData.hpp index 79690ef..622802c 100644 --- a/src/NSMethodSignature+ArgumentsFromData.hpp +++ b/src/NSMethodSignature+ArgumentsFromData.hpp @@ -31,6 +31,7 @@ using namespace godot; - (NSUInteger)totalArgumentSize; - (Array)arrayFromArgumentData:(const void *)data; +- (Array)arrayFromInvocationArguments:(NSInvocation *)invocation; @end diff --git a/src/NSMethodSignature+ArgumentsFromData.mm b/src/NSMethodSignature+ArgumentsFromData.mm index ea913f0..ca220fa 100644 --- a/src/NSMethodSignature+ArgumentsFromData.mm +++ b/src/NSMethodSignature+ArgumentsFromData.mm @@ -52,4 +52,12 @@ - (Array)arrayFromArgumentData:(const void *)data { return args; } +- (Array)arrayFromInvocationArguments:(NSInvocation *)invocation { + Array args; + for (int i = 0; i < self.numberOfArguments; i++) { + args.append(get_argument_variant(invocation, i)); + } + return args; +} + @end diff --git a/src/objc_conversions.hpp b/src/objc_conversions.hpp index 3f4ea63..671c0a4 100644 --- a/src/objc_conversions.hpp +++ b/src/objc_conversions.hpp @@ -33,6 +33,7 @@ namespace objcgdextension { Class class_from_string(const String& string); Protocol *protocol_from_string(const String& string); SEL to_selector(const String& string); +String to_string(SEL selector); String format_selector_call(id obj, const String& selector); Variant to_variant(NSObject *obj); diff --git a/src/objc_conversions.mm b/src/objc_conversions.mm index 53c6c68..6b623fb 100644 --- a/src/objc_conversions.mm +++ b/src/objc_conversions.mm @@ -46,6 +46,10 @@ SEL to_selector(const godot::String& string) { return sel_registerName(chars.get_data()); } +String to_string(SEL selector) { + return String(sel_getName(selector)); +} + String format_selector_call(id obj, const String& selector) { return String("%s[%s %s]") % Array::make( object_isClass(obj) ? "+" : "-", From 7ca123c6d8082cea8085a01e37c5af05ae7c0164 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 10:00:49 -0300 Subject: [PATCH 04/20] Add support for GDObject in conversions --- src/GDObject.hpp | 2 ++ src/GDObject.mm | 4 ++++ src/objc_conversions.mm | 10 +++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/GDObject.hpp b/src/GDObject.hpp index 042ae97..74ea119 100644 --- a/src/GDObject.hpp +++ b/src/GDObject.hpp @@ -36,6 +36,8 @@ using namespace godot; */ @interface GDObject : NSObject +@property(readonly) Variant variant; + + (instancetype)objectWithObject:(Object *)object; - (instancetype)initWithObject:(Object *)object; diff --git a/src/GDObject.mm b/src/GDObject.mm index 8d0232b..fa3f6ee 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -93,6 +93,10 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation { [super forwardInvocation:anInvocation]; } +- (Variant)variant { + return Variant(_obj); +} + + (BOOL)isCompatibleObject:(Object *)object { if (object) { return object->has_method("methodSignatureForSelector"); diff --git a/src/objc_conversions.mm b/src/objc_conversions.mm index 6b623fb..b0eb8c3 100644 --- a/src/objc_conversions.mm +++ b/src/objc_conversions.mm @@ -21,6 +21,7 @@ */ #include "objc_conversions.hpp" +#include "GDObject.hpp" #include "ObjectiveCClass.hpp" #include "ObjectiveCObject.hpp" @@ -73,6 +74,10 @@ Variant to_variant(NSObject *obj) { NSNumber *number = (NSNumber *) obj; return to_variant(number); } + else if ([obj isKindOfClass:GDObject.class]) { + GDObject *gdobj = (GDObject *) obj; + return gdobj.variant; + } else { return memnew(ObjectiveCObject(obj)); } @@ -177,8 +182,11 @@ Variant to_variant(NSData *data) { else if (auto objc_obj = Object::cast_to(obj)) { return objc_obj->get_obj(); } + else if ([GDObject isCompatibleObject:obj]) { + return [GDObject objectWithObject:obj]; + } else { - ERR_FAIL_V_MSG(nil, String("Conversion from '%s' to NSObject* is not supported yet.") % obj->get_class()); + ERR_FAIL_V_MSG(nil, String("Conversion from '%s' to NSObject* is not supported.") % obj->get_class()); } } From dab3e0ca4927fadbcdd74868b0849c3d360740f9 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:14:35 -0300 Subject: [PATCH 05/20] Add ObjectiveCAPI::wrap_object --- src/ObjectiveCAPI.hpp | 1 + src/ObjectiveCAPI.mm | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/ObjectiveCAPI.hpp b/src/ObjectiveCAPI.hpp index 371630a..10bd233 100644 --- a/src/ObjectiveCAPI.hpp +++ b/src/ObjectiveCAPI.hpp @@ -39,6 +39,7 @@ class ObjectiveCAPI : public RefCounted { ObjectiveCClass *find_class(const String& name) const; ObjectiveCObject *create_block(const String& objCTypes, const Callable& implementation) const; + ObjectiveCObject *wrap_object(Object *godot_object) const; static ObjectiveCAPI *get_singleton(); diff --git a/src/ObjectiveCAPI.mm b/src/ObjectiveCAPI.mm index 1acbcf0..30f0c13 100644 --- a/src/ObjectiveCAPI.mm +++ b/src/ObjectiveCAPI.mm @@ -21,8 +21,10 @@ */ #include "ObjectiveCAPI.hpp" +#include "GDObject.hpp" #include "GDCallableBlock.hpp" #include "ObjectiveCClass.hpp" +#include "ObjectiveCObject.hpp" #include "objc_conversions.hpp" #include @@ -58,6 +60,11 @@ } } +ObjectiveCObject *ObjectiveCAPI::wrap_object(Object *godot_object) const { + ERR_FAIL_COND_V_MSG(![GDObject isCompatibleObject:godot_object], nullptr, "Object must implement 'func methodSignatureForSelector(String) -> String'"); + return memnew(ObjectiveCObject([[GDObject alloc] initWithObject:godot_object], false)); +} + ObjectiveCAPI *ObjectiveCAPI::get_singleton() { return instance; } @@ -65,6 +72,7 @@ void ObjectiveCAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("find_class", "name"), &ObjectiveCAPI::find_class); ClassDB::bind_method(D_METHOD("create_block", "objc_types", "implementation"), &ObjectiveCAPI::create_block); + ClassDB::bind_method(D_METHOD("wrap_object", "godot_object"), &ObjectiveCAPI::wrap_object); } bool ObjectiveCAPI::_get(const StringName& name, Variant& r_value) { From 229dd74169a242b5da69924307a7b1811e001f33 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:15:01 -0300 Subject: [PATCH 06/20] Fix support for properties in GDObject --- src/GDObject.mm | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/GDObject.mm b/src/GDObject.mm index fa3f6ee..5b3b628 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -83,7 +83,7 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation { set_result_variant(anInvocation, result, args); return; } - else if (anInvocation.methodSignature.numberOfArguments == 0) { + else if (anInvocation.methodSignature.numberOfArguments == 2) { Variant property = _obj->get(methodName); Array string_holder; set_result_variant(anInvocation, property, string_holder); @@ -93,6 +93,16 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation { [super forwardInvocation:anInvocation]; } +- (id)valueForUndefinedKey:(NSString *)key { + if (_obj) { + Variant value = _obj->get(key.UTF8String); + return to_nsobject(value); + } + else { + return [super valueForUndefinedKey:key]; + } +} + - (Variant)variant { return Variant(_obj); } From 747c556e591a969b9fc39e8d16525b6705484fd8 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:15:38 -0300 Subject: [PATCH 07/20] Add unit test for GDObject properties --- test/unit_tests/GDObject.gd | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/unit_tests/GDObject.gd diff --git a/test/unit_tests/GDObject.gd b/test/unit_tests/GDObject.gd new file mode 100644 index 0000000..7394b5f --- /dev/null +++ b/test/unit_tests/GDObject.gd @@ -0,0 +1,20 @@ +extends RefCounted + +static var ObjectiveC = Engine.get_singleton("ObjectiveC") + +var my_property = 5 + + +func test_property() -> bool: + var objcObject = ObjectiveC.wrap_object(self) + assert(objcObject.my_property == my_property) + assert(objcObject.invoke("my_property") == my_property) + return true + + +func methodSignatureForSelector(sel: String) -> String: + match sel: + "my_property": + return "i@:" + _: + return "" From 45c335acff0c29a9fce5f13e7cf8f139b51f14ce Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:17:46 -0300 Subject: [PATCH 08/20] Add error message for invalid method signatures --- src/GDObject.mm | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/GDObject.mm b/src/GDObject.mm index 5b3b628..f0c4107 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -62,9 +62,18 @@ - (instancetype)copyWithZone:(NSZone *)zone { - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { @try { if (_obj) { - String methodSignature = _obj->call("methodSignatureForSelector", to_string(aSelector)); - if (!methodSignature.is_empty()) { - return [NSMethodSignature signatureWithObjCTypes:methodSignature.ascii().get_data()]; + String ctypes = _obj->call("methodSignatureForSelector", to_string(aSelector)); + if (!ctypes.is_empty()) { + NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:ctypes.ascii().get_data()]; + if (signature.numberOfArguments >= 2 + && [signature getArgumentTypeAtIndex:0][0] == '@' + && [signature getArgumentTypeAtIndex:1][0] == ':' + ) { + return signature; + } + else { + ERR_PRINT(String("Invalid method signature '%s': expected at least target and selector arguments (e.g.: 'v@:')") % ctypes); + } } } } From 383b722b175db04f2536086fec8cfe89e61bb6d1 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:18:31 -0300 Subject: [PATCH 09/20] Fix objc_invocation to not rely on `-[NSObject respondsToSelector:]` --- src/NSMethodSignature+ArgumentsFromData.hpp | 1 + src/NSMethodSignature+ArgumentsFromData.mm | 8 ++++++++ src/objc_invocation.mm | 12 ++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/NSMethodSignature+ArgumentsFromData.hpp b/src/NSMethodSignature+ArgumentsFromData.hpp index 622802c..ccf8b7d 100644 --- a/src/NSMethodSignature+ArgumentsFromData.hpp +++ b/src/NSMethodSignature+ArgumentsFromData.hpp @@ -30,6 +30,7 @@ using namespace godot; @interface NSMethodSignature (ArgumentsFromData) - (NSUInteger)totalArgumentSize; +- (String)completeSignature; - (Array)arrayFromArgumentData:(const void *)data; - (Array)arrayFromInvocationArguments:(NSInvocation *)invocation; diff --git a/src/NSMethodSignature+ArgumentsFromData.mm b/src/NSMethodSignature+ArgumentsFromData.mm index ca220fa..aac5212 100644 --- a/src/NSMethodSignature+ArgumentsFromData.mm +++ b/src/NSMethodSignature+ArgumentsFromData.mm @@ -38,6 +38,14 @@ - (NSUInteger)totalArgumentSize { return totalSize; } +- (String)completeSignature { + String signature(self.methodReturnType); + for (int i = 0; i < self.numberOfArguments; i++) { + signature += [self getArgumentTypeAtIndex:i]; + } + return signature; +} + - (Array)arrayFromArgumentData:(const void *)data { Array args; const uint8_t *ptr = (const uint8_t *) data; diff --git a/src/objc_invocation.mm b/src/objc_invocation.mm index 42a31ba..3dec381 100644 --- a/src/objc_invocation.mm +++ b/src/objc_invocation.mm @@ -21,6 +21,7 @@ */ #include "objc_invocation.hpp" +#include "NSMethodSignature+ArgumentsFromData.hpp" #include "ObjectiveCClass.hpp" #include "ObjectiveCObject.hpp" #include "objc_conversions.hpp" @@ -35,13 +36,16 @@ NSInvocation *prepare_and_invoke(id target, const String& selector, const godot::Variant **argv, GDExtensionInt argc) { SEL sel = to_selector(selector); - ERR_FAIL_COND_V_EDMSG( - ![target respondsToSelector:sel], + + NSMethodSignature *signature = [target methodSignatureForSelector:sel]; + ERR_FAIL_COND_V_MSG( + signature.numberOfArguments < 2 + || [signature getArgumentTypeAtIndex:0][0] != '@' + || [signature getArgumentTypeAtIndex:1][0] != ':', nil, - String("Invalid call to %s: selector not supported") % format_selector_call(target, selector) + String("Invalid method signature '%s': expected at least 2 arguments, target and selector") % signature.completeSignature ); - NSMethodSignature *signature = [target methodSignatureForSelector:sel]; int expected_argc = signature.numberOfArguments - 2; ERR_FAIL_COND_V_MSG( expected_argc != argc, From d434140d8586ddd97b4da8069ea278869e727447 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:30:20 -0300 Subject: [PATCH 10/20] Move NSInvocation arguments as Array to a new category --- src/GDObject.mm | 3 +- src/NSInvocation+Godot.hpp | 36 +++++++++++++++++++ src/NSInvocation+Godot.mm | 38 +++++++++++++++++++++ src/NSMethodSignature+ArgumentsFromData.hpp | 6 ++-- src/NSMethodSignature+ArgumentsFromData.mm | 8 ----- test/unit_tests/GDObject.gd | 13 +++++++ 6 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 src/NSInvocation+Godot.hpp create mode 100644 src/NSInvocation+Godot.mm diff --git a/src/GDObject.mm b/src/GDObject.mm index f0c4107..c0e94b9 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -22,6 +22,7 @@ #include "GDObject.hpp" #import "NSMethodSignature+ArgumentsFromData.hpp" +#import "NSInvocation+Godot.hpp" #import "objc_conversions.hpp" #import "objc_marshalling.hpp" @@ -87,7 +88,7 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation { String methodName = [GDObject godotNameForSelector:anInvocation.selector]; if (_obj) { if (_obj->has_method(methodName)) { - Array args = [anInvocation.methodSignature arrayFromInvocationArguments:anInvocation]; + Array args = anInvocation.argumentArray; Variant result = _obj->callv(methodName, args); set_result_variant(anInvocation, result, args); return; diff --git a/src/NSInvocation+Godot.hpp b/src/NSInvocation+Godot.hpp new file mode 100644 index 0000000..4e48f5b --- /dev/null +++ b/src/NSInvocation+Godot.hpp @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2023 Gil Barbosa Reis. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __NSINVOCATION_GODOT_HPP__ +#define __NSINVOCATION_GODOT_HPP__ + +#include +#include + +using namespace godot; + +@interface NSInvocation (Godot) + +@property(readonly) Array argumentArray; + +@end + +#endif // __NSINVOCATION_GODOT_HPP__ diff --git a/src/NSInvocation+Godot.mm b/src/NSInvocation+Godot.mm new file mode 100644 index 0000000..790e5f8 --- /dev/null +++ b/src/NSInvocation+Godot.mm @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2023 Gil Barbosa Reis. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "NSInvocation+Godot.hpp" + +#include "objc_marshalling.hpp" + +using namespace objcgdextension; + +@implementation NSInvocation (Godot) + +- (Array)argumentArray { + Array args; + for (int i = 2; i < self.methodSignature.numberOfArguments; i++) { + args.append(get_argument_variant(self, i)); + } + return args; +} + +@end diff --git a/src/NSMethodSignature+ArgumentsFromData.hpp b/src/NSMethodSignature+ArgumentsFromData.hpp index ccf8b7d..7e71406 100644 --- a/src/NSMethodSignature+ArgumentsFromData.hpp +++ b/src/NSMethodSignature+ArgumentsFromData.hpp @@ -29,10 +29,10 @@ using namespace godot; @interface NSMethodSignature (ArgumentsFromData) -- (NSUInteger)totalArgumentSize; -- (String)completeSignature; +@property(readonly) NSUInteger totalArgumentSize; +@property(readonly) String completeSignature; + - (Array)arrayFromArgumentData:(const void *)data; -- (Array)arrayFromInvocationArguments:(NSInvocation *)invocation; @end diff --git a/src/NSMethodSignature+ArgumentsFromData.mm b/src/NSMethodSignature+ArgumentsFromData.mm index aac5212..4266c3f 100644 --- a/src/NSMethodSignature+ArgumentsFromData.mm +++ b/src/NSMethodSignature+ArgumentsFromData.mm @@ -60,12 +60,4 @@ - (Array)arrayFromArgumentData:(const void *)data { return args; } -- (Array)arrayFromInvocationArguments:(NSInvocation *)invocation { - Array args; - for (int i = 0; i < self.numberOfArguments; i++) { - args.append(get_argument_variant(invocation, i)); - } - return args; -} - @end diff --git a/test/unit_tests/GDObject.gd b/test/unit_tests/GDObject.gd index 7394b5f..57c494a 100644 --- a/test/unit_tests/GDObject.gd +++ b/test/unit_tests/GDObject.gd @@ -12,9 +12,22 @@ func test_property() -> bool: return true +func test_method() -> bool: + var objcObject = ObjectiveC.wrap_object(self) + assert(objcObject.invoke("intToBool:", 1) == true) + assert(objcObject.invoke("intToBool:", 0) == false) + return true + + +func intToBool(value: int) -> bool: + return bool(value) + + func methodSignatureForSelector(sel: String) -> String: match sel: "my_property": return "i@:" + "intToBool:": + return "B@:i" _: return "" From 9843662c9f9b946c80a1f3769c9c87ad3b385942 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:31:40 -0300 Subject: [PATCH 11/20] Rename NSMethodSignature category ArgumentsFromData -> Godot --- src/GDCallableBlock.mm | 2 +- src/GDObject.mm | 1 - ...+ArgumentsFromData.hpp => NSMethodSignature+Godot.hpp} | 8 ++++---- ...re+ArgumentsFromData.mm => NSMethodSignature+Godot.mm} | 4 ++-- src/objc_invocation.mm | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) rename src/{NSMethodSignature+ArgumentsFromData.hpp => NSMethodSignature+Godot.hpp} (86%) rename src/{NSMethodSignature+ArgumentsFromData.mm => NSMethodSignature+Godot.mm} (95%) diff --git a/src/GDCallableBlock.mm b/src/GDCallableBlock.mm index fbf0145..cff59f0 100644 --- a/src/GDCallableBlock.mm +++ b/src/GDCallableBlock.mm @@ -21,7 +21,7 @@ */ #include "GDCallableBlock.hpp" -#include "NSMethodSignature+ArgumentsFromData.hpp" +#include "NSMethodSignature+Godot.hpp" #include diff --git a/src/GDObject.mm b/src/GDObject.mm index c0e94b9..bddc3f8 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -21,7 +21,6 @@ */ #include "GDObject.hpp" -#import "NSMethodSignature+ArgumentsFromData.hpp" #import "NSInvocation+Godot.hpp" #import "objc_conversions.hpp" #import "objc_marshalling.hpp" diff --git a/src/NSMethodSignature+ArgumentsFromData.hpp b/src/NSMethodSignature+Godot.hpp similarity index 86% rename from src/NSMethodSignature+ArgumentsFromData.hpp rename to src/NSMethodSignature+Godot.hpp index 7e71406..55e64af 100644 --- a/src/NSMethodSignature+ArgumentsFromData.hpp +++ b/src/NSMethodSignature+Godot.hpp @@ -19,15 +19,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef __NSMETHOD_SIGNATURE_ARGUMENTS_FROM_DATA_HPP__ -#define __NSMETHOD_SIGNATURE_ARGUMENTS_FROM_DATA_HPP__ +#ifndef __NSMETHOD_SIGNATURE_GODOT_HPP__ +#define __NSMETHOD_SIGNATURE_GODOT_HPP__ #include #include using namespace godot; -@interface NSMethodSignature (ArgumentsFromData) +@interface NSMethodSignature (Godot) @property(readonly) NSUInteger totalArgumentSize; @property(readonly) String completeSignature; @@ -36,4 +36,4 @@ using namespace godot; @end -#endif // __NSMETHOD_SIGNATURE_ARGUMENTS_FROM_DATA_HPP__ +#endif // __NSMETHOD_SIGNATURE_GODOT_HPP__ diff --git a/src/NSMethodSignature+ArgumentsFromData.mm b/src/NSMethodSignature+Godot.mm similarity index 95% rename from src/NSMethodSignature+ArgumentsFromData.mm rename to src/NSMethodSignature+Godot.mm index 4266c3f..6048c9d 100644 --- a/src/NSMethodSignature+ArgumentsFromData.mm +++ b/src/NSMethodSignature+Godot.mm @@ -19,13 +19,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#import "NSMethodSignature+ArgumentsFromData.hpp" +#import "NSMethodSignature+Godot.hpp" #include "objc_marshalling.hpp" using namespace objcgdextension; -@implementation NSMethodSignature (ArgumentsFromData) +@implementation NSMethodSignature (Godot) - (NSUInteger)totalArgumentSize { NSUInteger totalSize = 0; diff --git a/src/objc_invocation.mm b/src/objc_invocation.mm index 3dec381..1a647e7 100644 --- a/src/objc_invocation.mm +++ b/src/objc_invocation.mm @@ -21,7 +21,7 @@ */ #include "objc_invocation.hpp" -#include "NSMethodSignature+ArgumentsFromData.hpp" +#include "NSMethodSignature+Godot.hpp" #include "ObjectiveCClass.hpp" #include "ObjectiveCObject.hpp" #include "objc_conversions.hpp" From 7bd98569475f3452db4e5141e438468a06ef09a8 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:45:11 -0300 Subject: [PATCH 12/20] Fix build workflow cache key --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff7a3bb..eae575a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,9 @@ jobs: uses: actions/cache@v3 with: path: .scons-cache/ - key: ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }} + key: ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }}-${{ hashfiles('.gitmodules', 'src/**.{hpp,cpp}') }} + restore-keys: | + ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }} - name: Build artifact run: | scons platform=${{ matrix.platform }} ios_simulator=${{ matrix.simulator }} arch=${{ matrix.arch }} target=${{ matrix.target }} From 912051439eef43f01f5387abc2d5474e5d34a183 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 11:49:58 -0300 Subject: [PATCH 13/20] Avoid processing data in branch that won't use it --- src/GDObject.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GDObject.mm b/src/GDObject.mm index bddc3f8..1fec15e 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -84,8 +84,8 @@ - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { } - (void)forwardInvocation:(NSInvocation *)anInvocation { - String methodName = [GDObject godotNameForSelector:anInvocation.selector]; if (_obj) { + String methodName = [GDObject godotNameForSelector:anInvocation.selector]; if (_obj->has_method(methodName)) { Array args = anInvocation.argumentArray; Variant result = _obj->callv(methodName, args); From 31e0b582e1a6fc5b9229e403ace6cae87abb3257 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 12:15:13 -0300 Subject: [PATCH 14/20] Fix support for NSInvocation returning void --- src/objc_marshalling.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/objc_marshalling.mm b/src/objc_marshalling.mm index d6285fe..74b8949 100644 --- a/src/objc_marshalling.mm +++ b/src/objc_marshalling.mm @@ -237,6 +237,9 @@ bool set_variant(const char *objc_type, TDeref buffer, const Variant& value, Arr } } + case 'v': + return true; + case '*': { PackedByteArray chars; if (value.get_type() == Variant::PACKED_BYTE_ARRAY) { From 35b05e63e805778ea5ef7be3c423eb086e005107 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Thu, 12 Oct 2023 12:16:02 -0300 Subject: [PATCH 15/20] Add unit test for GDObject handling notifications --- test/unit_tests/GDObject.gd | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/test/unit_tests/GDObject.gd b/test/unit_tests/GDObject.gd index 57c494a..aefadcc 100644 --- a/test/unit_tests/GDObject.gd +++ b/test/unit_tests/GDObject.gd @@ -1,21 +1,34 @@ extends RefCounted static var ObjectiveC = Engine.get_singleton("ObjectiveC") +const NOTIFICATION_NAME = "my cool notification" +const NOTIFICATION_USER_INFO = { hello = "world" } var my_property = 5 +var notification_called = false func test_property() -> bool: - var objcObject = ObjectiveC.wrap_object(self) - assert(objcObject.my_property == my_property) - assert(objcObject.invoke("my_property") == my_property) + var self_objc = ObjectiveC.wrap_object(self) + assert(self_objc.my_property == my_property) + assert(self_objc.invoke("my_property") == my_property) return true func test_method() -> bool: - var objcObject = ObjectiveC.wrap_object(self) - assert(objcObject.invoke("intToBool:", 1) == true) - assert(objcObject.invoke("intToBool:", 0) == false) + var self_objc = ObjectiveC.wrap_object(self) + assert(self_objc.invoke("intToBool:", 1) == true) + assert(self_objc.invoke("intToBool:", 0) == false) + return true + + +func test_notification() -> bool: + var self_objc = ObjectiveC.wrap_object(self) + var NSNotificationCenter = ObjectiveC.NSNotificationCenter.defaultCenter + NSNotificationCenter.invoke("addObserver:selector:name:object:", self_objc, "handleNotification:", NOTIFICATION_NAME, null) + NSNotificationCenter.invoke("postNotificationName:object:userInfo:", NOTIFICATION_NAME, null, NOTIFICATION_USER_INFO) + NSNotificationCenter.invoke("removeObserver:", self_objc) + assert(notification_called, "Notification was not called") return true @@ -23,11 +36,19 @@ func intToBool(value: int) -> bool: return bool(value) +func handleNotification(notification): + assert(notification.name == NOTIFICATION_NAME) + assert(notification.userInfo.to_dictionary() == NOTIFICATION_USER_INFO) + notification_called = true + + func methodSignatureForSelector(sel: String) -> String: match sel: "my_property": return "i@:" "intToBool:": return "B@:i" + "handleNotification:": + return "v@:@" _: return "" From da076ab76c5998bbeb07713c5fde8bfa54bd57ea Mon Sep 17 00:00:00 2001 From: gilzoide Date: Fri, 13 Oct 2023 08:01:01 -0300 Subject: [PATCH 16/20] Fix cache keys --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eae575a..df9d715 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,9 +32,9 @@ jobs: uses: actions/cache@v3 with: path: .scons-cache/ - key: ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }}-${{ hashfiles('.gitmodules', 'src/**.{hpp,cpp}') }} + key: ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }}-${{ hashfiles('.gitmodules', 'src/**') }} restore-keys: | - ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }} + ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }}- - name: Build artifact run: | scons platform=${{ matrix.platform }} ios_simulator=${{ matrix.simulator }} arch=${{ matrix.arch }} target=${{ matrix.target }} From b864a6440b94d604fbce73353636d328ccd0b3e5 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Fri, 13 Oct 2023 08:05:05 -0300 Subject: [PATCH 17/20] Override `-[GDObject respondsToSelector:]` --- src/GDObject.mm | 4 ++++ test/unit_tests/GDObject.gd | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/GDObject.mm b/src/GDObject.mm index 1fec15e..be6fe88 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -59,6 +59,10 @@ - (instancetype)copyWithZone:(NSZone *)zone { return [self retain]; } +- (BOOL)respondsToSelector:(SEL)aSelector { + return [self methodSignatureForSelector:aSelector] != nil; +} + - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { @try { if (_obj) { diff --git a/test/unit_tests/GDObject.gd b/test/unit_tests/GDObject.gd index aefadcc..406274d 100644 --- a/test/unit_tests/GDObject.gd +++ b/test/unit_tests/GDObject.gd @@ -11,12 +11,14 @@ var notification_called = false func test_property() -> bool: var self_objc = ObjectiveC.wrap_object(self) assert(self_objc.my_property == my_property) + assert(self_objc.responds_to_selector("my_property")) assert(self_objc.invoke("my_property") == my_property) return true func test_method() -> bool: var self_objc = ObjectiveC.wrap_object(self) + assert(self_objc.responds_to_selector("intToBool:")) assert(self_objc.invoke("intToBool:", 1) == true) assert(self_objc.invoke("intToBool:", 0) == false) return true From ff423df9adf42f91d0506b86fc590fe74a842fed Mon Sep 17 00:00:00 2001 From: gilzoide Date: Fri, 13 Oct 2023 08:29:16 -0300 Subject: [PATCH 18/20] Wrap objects as weak references by default in GDObject --- src/GDObject.hpp | 4 ++-- src/GDObject.mm | 20 +++++++++++++------- src/ObjectiveCAPI.hpp | 2 +- src/ObjectiveCAPI.mm | 6 +++--- src/objc_conversions.mm | 2 +- test/unit_tests/GDObject.gd | 4 +--- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/GDObject.hpp b/src/GDObject.hpp index 74ea119..ce43d32 100644 --- a/src/GDObject.hpp +++ b/src/GDObject.hpp @@ -38,8 +38,8 @@ using namespace godot; @property(readonly) Variant variant; -+ (instancetype)objectWithObject:(Object *)object; -- (instancetype)initWithObject:(Object *)object; ++ (instancetype)objectWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference; +- (instancetype)initWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference; + (BOOL)isCompatibleObject:(Object *)object; + (String)godotNameForSelector:(SEL)selector; diff --git a/src/GDObject.mm b/src/GDObject.mm index be6fe88..07376ea 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -32,25 +32,31 @@ @implementation GDObject { Object *_obj; + BOOL _shouldRetainReference; } -+ (instancetype)objectWithObject:(Object *)object { - return [[[self alloc] initWithObject:object] autorelease]; ++ (instancetype)objectWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference { + return [[[self alloc] initWithObject:object retainingReference:shouldRetainReference] autorelease]; } -- (instancetype)initWithObject:(Object *)object { +- (instancetype)initWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference { if (self = [super init]) { _obj = object; - if (RefCounted *ref = Object::cast_to(object)) { - ref->reference(); + _shouldRetainReference = shouldRetainReference; + if (shouldRetainReference) { + if (RefCounted *ref = Object::cast_to(object)) { + ref->reference(); + } } } return self; } - (void)dealloc { - if (RefCounted *ref = Object::cast_to(_obj)) { - ref->unreference(); + if (_shouldRetainReference) { + if (RefCounted *ref = Object::cast_to(_obj)) { + ref->unreference(); + } } [super dealloc]; } diff --git a/src/ObjectiveCAPI.hpp b/src/ObjectiveCAPI.hpp index 10bd233..a74dbce 100644 --- a/src/ObjectiveCAPI.hpp +++ b/src/ObjectiveCAPI.hpp @@ -39,7 +39,7 @@ class ObjectiveCAPI : public RefCounted { ObjectiveCClass *find_class(const String& name) const; ObjectiveCObject *create_block(const String& objCTypes, const Callable& implementation) const; - ObjectiveCObject *wrap_object(Object *godot_object) const; + ObjectiveCObject *wrap_object(Object *godot_object, bool retaining_reference = false) const; static ObjectiveCAPI *get_singleton(); diff --git a/src/ObjectiveCAPI.mm b/src/ObjectiveCAPI.mm index 30f0c13..9463385 100644 --- a/src/ObjectiveCAPI.mm +++ b/src/ObjectiveCAPI.mm @@ -60,9 +60,9 @@ } } -ObjectiveCObject *ObjectiveCAPI::wrap_object(Object *godot_object) const { +ObjectiveCObject *ObjectiveCAPI::wrap_object(Object *godot_object, bool retaining_reference) const { ERR_FAIL_COND_V_MSG(![GDObject isCompatibleObject:godot_object], nullptr, "Object must implement 'func methodSignatureForSelector(String) -> String'"); - return memnew(ObjectiveCObject([[GDObject alloc] initWithObject:godot_object], false)); + return memnew(ObjectiveCObject([[GDObject alloc] initWithObject:godot_object retainingReference:retaining_reference], false)); } ObjectiveCAPI *ObjectiveCAPI::get_singleton() { @@ -72,7 +72,7 @@ void ObjectiveCAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("find_class", "name"), &ObjectiveCAPI::find_class); ClassDB::bind_method(D_METHOD("create_block", "objc_types", "implementation"), &ObjectiveCAPI::create_block); - ClassDB::bind_method(D_METHOD("wrap_object", "godot_object"), &ObjectiveCAPI::wrap_object); + ClassDB::bind_method(D_METHOD("wrap_object", "object", "retaining_reference"), &ObjectiveCAPI::wrap_object, DEFVAL(false)); } bool ObjectiveCAPI::_get(const StringName& name, Variant& r_value) { diff --git a/src/objc_conversions.mm b/src/objc_conversions.mm index b0eb8c3..4096ff6 100644 --- a/src/objc_conversions.mm +++ b/src/objc_conversions.mm @@ -183,7 +183,7 @@ Variant to_variant(NSData *data) { return objc_obj->get_obj(); } else if ([GDObject isCompatibleObject:obj]) { - return [GDObject objectWithObject:obj]; + return [GDObject objectWithObject:obj retainingReference:NO]; } else { ERR_FAIL_V_MSG(nil, String("Conversion from '%s' to NSObject* is not supported.") % obj->get_class()); diff --git a/test/unit_tests/GDObject.gd b/test/unit_tests/GDObject.gd index 406274d..47bb0cf 100644 --- a/test/unit_tests/GDObject.gd +++ b/test/unit_tests/GDObject.gd @@ -6,10 +6,10 @@ const NOTIFICATION_USER_INFO = { hello = "world" } var my_property = 5 var notification_called = false +var self_objc = ObjectiveC.wrap_object(self) func test_property() -> bool: - var self_objc = ObjectiveC.wrap_object(self) assert(self_objc.my_property == my_property) assert(self_objc.responds_to_selector("my_property")) assert(self_objc.invoke("my_property") == my_property) @@ -17,7 +17,6 @@ func test_property() -> bool: func test_method() -> bool: - var self_objc = ObjectiveC.wrap_object(self) assert(self_objc.responds_to_selector("intToBool:")) assert(self_objc.invoke("intToBool:", 1) == true) assert(self_objc.invoke("intToBool:", 0) == false) @@ -25,7 +24,6 @@ func test_method() -> bool: func test_notification() -> bool: - var self_objc = ObjectiveC.wrap_object(self) var NSNotificationCenter = ObjectiveC.NSNotificationCenter.defaultCenter NSNotificationCenter.invoke("addObserver:selector:name:object:", self_objc, "handleNotification:", NOTIFICATION_NAME, null) NSNotificationCenter.invoke("postNotificationName:object:userInfo:", NOTIFICATION_NAME, null, NOTIFICATION_USER_INFO) From df33f3827abd1ed83cdce7bdc0f32df7a79c811c Mon Sep 17 00:00:00 2001 From: gilzoide Date: Fri, 13 Oct 2023 08:29:46 -0300 Subject: [PATCH 19/20] Use is_instance_valid in GDObject instead of simple null pointer check --- src/GDObject.mm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/GDObject.mm b/src/GDObject.mm index 07376ea..745af51 100644 --- a/src/GDObject.mm +++ b/src/GDObject.mm @@ -27,6 +27,7 @@ #include #include +#include using namespace objcgdextension; @@ -71,7 +72,7 @@ - (BOOL)respondsToSelector:(SEL)aSelector { - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { @try { - if (_obj) { + if (UtilityFunctions::is_instance_valid(_obj)) { String ctypes = _obj->call("methodSignatureForSelector", to_string(aSelector)); if (!ctypes.is_empty()) { NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:ctypes.ascii().get_data()]; @@ -94,7 +95,7 @@ - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { } - (void)forwardInvocation:(NSInvocation *)anInvocation { - if (_obj) { + if (UtilityFunctions::is_instance_valid(_obj)) { String methodName = [GDObject godotNameForSelector:anInvocation.selector]; if (_obj->has_method(methodName)) { Array args = anInvocation.argumentArray; @@ -113,7 +114,7 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation { } - (id)valueForUndefinedKey:(NSString *)key { - if (_obj) { + if (UtilityFunctions::is_instance_valid(_obj)) { Variant value = _obj->get(key.UTF8String); return to_nsobject(value); } From 66f0127f0bb7db5e289e3a9c1b1ceb554bdfdc15 Mon Sep 17 00:00:00 2001 From: gilzoide Date: Fri, 13 Oct 2023 08:32:49 -0300 Subject: [PATCH 20/20] Update README [skip ci] --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0b9c462..3b6a85f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ Use at your own risk. - `to_dictionary` method for converting Objective-C objects that support enumeration and the `objectForKey:` message, like `NSDictionary`, to Godot `Dictionary` - Other useful methods, like `is_kind_of_class`, `responds_to_selector` and `conforms_to_protocol` - Supports creating blocks from a `Callable` and the corresponding Objective-C method signature +- Supports passing Godot objects to Objective-C if they implement `func methodSignatureForSelector(String) -> String`. + Check out [GDObject.gd](test/unit_tests/GDObject.gd) for sample code. ## Caveats