Skip to content

Commit

Permalink
Merge pull request #7 from gilzoide/gdobject
Browse files Browse the repository at this point in the history
Add support for Godot objects receiving Objective-C messages
  • Loading branch information
gilzoide committed Oct 13, 2023
2 parents c338565 + 66f0127 commit 5c5d702
Show file tree
Hide file tree
Showing 17 changed files with 419 additions and 15 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/**') }}
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 }}
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/GDCallableBlock.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*/
#include "GDCallableBlock.hpp"

#include "NSMethodSignature+ArgumentsFromData.hpp"
#include "NSMethodSignature+Godot.hpp"

#include <objc/runtime.h>

Expand Down
49 changes: 49 additions & 0 deletions src/GDObject.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* 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 <Foundation/Foundation.h>

#include <godot_cpp/variant/variant.hpp>

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

@property(readonly) Variant variant;

+ (instancetype)objectWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference;
- (instancetype)initWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference;

+ (BOOL)isCompatibleObject:(Object *)object;
+ (String)godotNameForSelector:(SEL)selector;

@end

#endif // __GDOOBJECT_HPP__
150 changes: 150 additions & 0 deletions src/GDObject.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* 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"

#import "NSInvocation+Godot.hpp"
#import "objc_conversions.hpp"
#import "objc_marshalling.hpp"

#include <godot_cpp/core/error_macros.hpp>
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

using namespace objcgdextension;

@implementation GDObject {
Object *_obj;
BOOL _shouldRetainReference;
}

+ (instancetype)objectWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference {
return [[[self alloc] initWithObject:object retainingReference:shouldRetainReference] autorelease];
}

- (instancetype)initWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference {
if (self = [super init]) {
_obj = object;
_shouldRetainReference = shouldRetainReference;
if (shouldRetainReference) {
if (RefCounted *ref = Object::cast_to<RefCounted>(object)) {
ref->reference();
}
}
}
return self;
}

- (void)dealloc {
if (_shouldRetainReference) {
if (RefCounted *ref = Object::cast_to<RefCounted>(_obj)) {
ref->unreference();
}
}
[super dealloc];
}

- (instancetype)copyWithZone:(NSZone *)zone {
return [self retain];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
return [self methodSignatureForSelector:aSelector] != nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
@try {
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()];
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);
}
}
}
}
@catch (NSException *ex) {
ERR_PRINT(ex.description.UTF8String);
}
return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (UtilityFunctions::is_instance_valid(_obj)) {
String methodName = [GDObject godotNameForSelector:anInvocation.selector];
if (_obj->has_method(methodName)) {
Array args = anInvocation.argumentArray;
Variant result = _obj->callv(methodName, args);
set_result_variant(anInvocation, result, args);
return;
}
else if (anInvocation.methodSignature.numberOfArguments == 2) {
Variant property = _obj->get(methodName);
Array string_holder;
set_result_variant(anInvocation, property, string_holder);
return;
}
}
[super forwardInvocation:anInvocation];
}

- (id)valueForUndefinedKey:(NSString *)key {
if (UtilityFunctions::is_instance_valid(_obj)) {
Variant value = _obj->get(key.UTF8String);
return to_nsobject(value);
}
else {
return [super valueForUndefinedKey:key];
}
}

- (Variant)variant {
return Variant(_obj);
}

+ (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
36 changes: 36 additions & 0 deletions src/NSInvocation+Godot.hpp
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>
#include <godot_cpp/variant/variant.hpp>

using namespace godot;

@interface NSInvocation (Godot)

@property(readonly) Array argumentArray;

@end

#endif // __NSINVOCATION_GODOT_HPP__
38 changes: 38 additions & 0 deletions src/NSInvocation+Godot.mm
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,21 @@
* 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 <Foundation/Foundation.h>
#include <godot_cpp/variant/variant.hpp>

using namespace godot;

@interface NSMethodSignature (ArgumentsFromData)
@interface NSMethodSignature (Godot)

@property(readonly) NSUInteger totalArgumentSize;
@property(readonly) String completeSignature;

- (NSUInteger)totalArgumentSize;
- (Array)arrayFromArgumentData:(const void *)data;

@end

#endif // __NSMETHOD_SIGNATURE_ARGUMENTS_FROM_DATA_HPP__
#endif // __NSMETHOD_SIGNATURE_GODOT_HPP__
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/ObjectiveCAPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, bool retaining_reference = false) const;

static ObjectiveCAPI *get_singleton();

Expand Down

0 comments on commit 5c5d702

Please sign in to comment.