Created
December 2, 2023 05:32
-
-
Save migueldeicaza/1884e40a485ade88eff8b4a4c762e0d9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Package.swift b/Package.swift | |
index cfc1412..d0a2ff0 100644 | |
--- a/Package.swift | |
+++ b/Package.swift | |
@@ -117,8 +117,8 @@ targets.append(contentsOf: [ | |
// Godot runtime as a library | |
.binaryTarget( | |
name: "libgodot_tests", | |
- url: "https://github.com/migueldeicaza/SwiftGodotKit/releases/download/v1.0.1/libgodot.xcframework.zip", | |
- checksum: "bb6ec0946311a71f1eba7ad393c0adf7b8f34a2389d8234ff500b2764b0c6ba5" | |
+ url: "https://github.com/migueldeicaza/SwiftGodotKit/releases/download/v4.1.99/libgodot.xcframework.zip", | |
+ checksum: "c8ddf62be6c00eacc36bd2dafe8d424c0b374833efe80546f6ee76bd27cee84e" | |
), | |
// Base functionality for Godot runtime dependant tests | |
diff --git a/Sources/SwiftGodot/Core/NIOLock.swift b/Sources/SwiftGodot/Core/NIOLock.swift | |
index 9ef1803..82ada61 100644 | |
--- a/Sources/SwiftGodot/Core/NIOLock.swift | |
+++ b/Sources/SwiftGodot/Core/NIOLock.swift | |
@@ -1,8 +1,253 @@ | |
+//===----------------------------------------------------------------------===// | |
// | |
-// File.swift | |
-// | |
+// This source file is part of the SwiftNIO open source project | |
// | |
-// Created by Miguel de Icaza on 12/2/23. | |
+// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors | |
+// Licensed under Apache License v2.0 | |
// | |
+// See LICENSE.txt for license information | |
+// See CONTRIBUTORS.txt for the list of SwiftNIO project authors | |
+// | |
+// SPDX-License-Identifier: Apache-2.0 | |
+// | |
+//===----------------------------------------------------------------------===// | |
+ | |
+#if canImport(Darwin) | |
+import Darwin | |
+#elseif os(Windows) | |
+import ucrt | |
+import WinSDK | |
+#elseif canImport(Glibc) | |
+import Glibc | |
+#elseif canImport(Musl) | |
+import Musl | |
+#else | |
+#error("The concurrency NIOLock module was unable to identify your C library.") | |
+#endif | |
+ | |
+#if os(Windows) | |
+@usableFromInline | |
+typealias LockPrimitive = SRWLOCK | |
+#else | |
+@usableFromInline | |
+typealias LockPrimitive = pthread_mutex_t | |
+#endif | |
+ | |
+@usableFromInline | |
+enum LockOperations { } | |
+ | |
+extension LockOperations { | |
+ @inlinable | |
+ static func create(_ mutex: UnsafeMutablePointer<LockPrimitive>) { | |
+ mutex.assertValidAlignment() | |
+ | |
+#if os(Windows) | |
+ InitializeSRWLock(mutex) | |
+#else | |
+ var attr = pthread_mutexattr_t() | |
+ pthread_mutexattr_init(&attr) | |
+// debugOnly { | |
+// pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) | |
+// } | |
+ | |
+ let err = pthread_mutex_init(mutex, &attr) | |
+ precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") | |
+#endif | |
+ } | |
+ | |
+ @inlinable | |
+ static func destroy(_ mutex: UnsafeMutablePointer<LockPrimitive>) { | |
+ mutex.assertValidAlignment() | |
+ | |
+#if os(Windows) | |
+ // SRWLOCK does not need to be free'd | |
+#else | |
+ let err = pthread_mutex_destroy(mutex) | |
+ precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") | |
+#endif | |
+ } | |
+ | |
+ @inlinable | |
+ static func lock(_ mutex: UnsafeMutablePointer<LockPrimitive>) { | |
+ mutex.assertValidAlignment() | |
+ | |
+#if os(Windows) | |
+ AcquireSRWLockExclusive(mutex) | |
+#else | |
+ let err = pthread_mutex_lock(mutex) | |
+ precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") | |
+#endif | |
+ } | |
+ | |
+ @inlinable | |
+ static func unlock(_ mutex: UnsafeMutablePointer<LockPrimitive>) { | |
+ mutex.assertValidAlignment() | |
+ | |
+#if os(Windows) | |
+ ReleaseSRWLockExclusive(mutex) | |
+#else | |
+ let err = pthread_mutex_unlock(mutex) | |
+ precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") | |
+#endif | |
+ } | |
+} | |
+ | |
+// Tail allocate both the mutex and a generic value using ManagedBuffer. | |
+// Both the header pointer and the elements pointer are stable for | |
+// the class's entire lifetime. | |
+// | |
+// However, for safety reasons, we elect to place the lock in the "elements" | |
+// section of the buffer instead of the head. The reasoning here is subtle, | |
+// so buckle in. | |
+// | |
+// _As a practical matter_, the implementation of ManagedBuffer ensures that | |
+// the pointer to the header is stable across the lifetime of the class, and so | |
+// each time you call `withUnsafeMutablePointers` or `withUnsafeMutablePointerToHeader` | |
+// the value of the header pointer will be the same. This is because ManagedBuffer uses | |
+// `Builtin.addressOf` to load the value of the header, and that does ~magic~ to ensure | |
+// that it does not invoke any weird Swift accessors that might copy the value. | |
+// | |
+// _However_, the header is also available via the `.header` field on the ManagedBuffer. | |
+// This presents a problem! The reason there's an issue is that `Builtin.addressOf` and friends | |
+// do not interact with Swift's exclusivity model. That is, the various `with` functions do not | |
+// conceptually trigger a mutating access to `.header`. For elements this isn't a concern because | |
+// there's literally no other way to perform the access, but for `.header` it's entirely possible | |
+// to accidentally recursively read it. | |
+// | |
+// Our implementation is free from these issues, so we don't _really_ need to worry about it. | |
+// However, out of an abundance of caution, we store the Value in the header, and the LockPrimitive | |
+// in the trailing elements. We still don't use `.header`, but it's better to be safe than sorry, | |
+// and future maintainers will be happier that we were cautious. | |
+// | |
+// See also: https://github.com/apple/swift/pull/40000 | |
+@usableFromInline | |
+final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> { | |
+ | |
+ @inlinable | |
+ static func create(value: Value) -> Self { | |
+ let buffer = Self.create(minimumCapacity: 1) { _ in | |
+ return value | |
+ } | |
+ let storage = unsafeDowncast(buffer, to: Self.self) | |
+ | |
+ storage.withUnsafeMutablePointers { _, lockPtr in | |
+ LockOperations.create(lockPtr) | |
+ } | |
+ | |
+ return storage | |
+ } | |
+ | |
+ @inlinable | |
+ func lock() { | |
+ self.withUnsafeMutablePointerToElements { lockPtr in | |
+ LockOperations.lock(lockPtr) | |
+ } | |
+ } | |
+ | |
+ @inlinable | |
+ func unlock() { | |
+ self.withUnsafeMutablePointerToElements { lockPtr in | |
+ LockOperations.unlock(lockPtr) | |
+ } | |
+ } | |
+ | |
+ @inlinable | |
+ deinit { | |
+ self.withUnsafeMutablePointerToElements { lockPtr in | |
+ LockOperations.destroy(lockPtr) | |
+ } | |
+ } | |
+ | |
+ @inlinable | |
+ func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T { | |
+ try self.withUnsafeMutablePointerToElements { lockPtr in | |
+ return try body(lockPtr) | |
+ } | |
+ } | |
+ | |
+ @inlinable | |
+ func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T { | |
+ try self.withUnsafeMutablePointers { valuePtr, lockPtr in | |
+ LockOperations.lock(lockPtr) | |
+ defer { LockOperations.unlock(lockPtr) } | |
+ return try mutate(&valuePtr.pointee) | |
+ } | |
+ } | |
+} | |
+ | |
+extension LockStorage: @unchecked Sendable { } | |
+ | |
+/// A threading lock based on `libpthread` instead of `libdispatch`. | |
+/// | |
+/// - note: ``NIOLock`` has reference semantics. | |
+/// | |
+/// This object provides a lock on top of a single `pthread_mutex_t`. This kind | |
+/// of lock is safe to use with `libpthread`-based threading models, such as the | |
+/// one used by NIO. On Windows, the lock is based on the substantially similar | |
+/// `SRWLOCK` type. | |
+public struct NIOLock { | |
+ @usableFromInline | |
+ internal let _storage: LockStorage<Void> | |
+ | |
+ /// Create a new lock. | |
+ @inlinable | |
+ public init() { | |
+ self._storage = .create(value: ()) | |
+ } | |
+ | |
+ /// Acquire the lock. | |
+ /// | |
+ /// Whenever possible, consider using `withLock` instead of this method and | |
+ /// `unlock`, to simplify lock handling. | |
+ @inlinable | |
+ public func lock() { | |
+ self._storage.lock() | |
+ } | |
+ | |
+ /// Release the lock. | |
+ /// | |
+ /// Whenever possible, consider using `withLock` instead of this method and | |
+ /// `lock`, to simplify lock handling. | |
+ @inlinable | |
+ public func unlock() { | |
+ self._storage.unlock() | |
+ } | |
+ | |
+ @inlinable | |
+ internal func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T { | |
+ return try self._storage.withLockPrimitive(body) | |
+ } | |
+} | |
+ | |
+extension NIOLock { | |
+ /// Acquire the lock for the duration of the given block. | |
+ /// | |
+ /// This convenience method should be preferred to `lock` and `unlock` in | |
+ /// most situations, as it ensures that the lock will be released regardless | |
+ /// of how `body` exits. | |
+ /// | |
+ /// - Parameter body: The block to execute while holding the lock. | |
+ /// - Returns: The value returned by the block. | |
+ @inlinable | |
+ public func withLock<T>(_ body: () throws -> T) rethrows -> T { | |
+ self.lock() | |
+ defer { | |
+ self.unlock() | |
+ } | |
+ return try body() | |
+ } | |
+ | |
+ @inlinable | |
+ public func withLockVoid(_ body: () throws -> Void) rethrows -> Void { | |
+ try self.withLock(body) | |
+ } | |
+} | |
+ | |
+extension NIOLock: Sendable {} | |
-import Foundation | |
+extension UnsafeMutablePointer { | |
+ @inlinable | |
+ func assertValidAlignment() { | |
+ assert(UInt(bitPattern: self) % UInt(MemoryLayout<Pointee>.alignment) == 0) | |
+ } | |
+} | |
diff --git a/Sources/SwiftGodot/Core/Wrapped.swift b/Sources/SwiftGodot/Core/Wrapped.swift | |
index 1d1b6cd..fb8a9c0 100644 | |
--- a/Sources/SwiftGodot/Core/Wrapped.swift | |
+++ b/Sources/SwiftGodot/Core/Wrapped.swift | |
@@ -136,9 +136,10 @@ open class Wrapped: Equatable, Identifiable, Hashable { | |
/// when a class is initialized with the empty constructor - this means that | |
/// subclasses will have a different name than the subclass. | |
public required init () { | |
- guard let godotObject = gi.classdb_construct_object (&Self.godotClassName.content) else { | |
+ guard let godotObject = bindingObject ?? gi.classdb_construct_object (&Self.godotClassName.content) else { | |
fatalError("SWIFT: It was not possible to construct a \(Self.godotClassName.description)") | |
} | |
+ bindingObject = nil | |
handle = UnsafeRawPointer(godotObject) | |
bindGodotInstance(instance: self) | |
@@ -178,10 +179,15 @@ func bindGodotInstance(instance: some Wrapped) { | |
var callbacks: GDExtensionInstanceBindingCallbacks | |
if frameworkType { | |
callbacks = Wrapped.frameworkTypeBindingCallback | |
- liveFrameworkObjects [handle] = instance | |
} else { | |
callbacks = Wrapped.userTypeBindingCallback | |
- liveSubtypedObjects [handle] = instance | |
+ } | |
+ tableLock.withLockVoid { | |
+ if frameworkType { | |
+ liveFrameworkObjects [handle] = instance | |
+ } else { | |
+ liveSubtypedObjects [handle] = instance | |
+ } | |
} | |
gi.object_set_instance_binding(UnsafeMutableRawPointer (mutating: handle), token, retain.toOpaque(), &callbacks) | |
@@ -197,6 +203,7 @@ func register<T:Wrapped> (type name: StringName, parent: StringName, type: T.Typ | |
return type.getVirtualDispatcher(name: StringName (fromPtr: name)) | |
} | |
+ #if legacy | |
var info = GDExtensionClassCreationInfo () | |
info.create_instance_func = createFunc(_:) | |
info.free_instance_func = freeFunc(_:_:) | |
@@ -211,6 +218,24 @@ func register<T:Wrapped> (type name: StringName, parent: StringName, type: T.Typ | |
gi.classdb_register_extension_class (library, namePtr, parentPtr, &info) | |
} | |
} | |
+ #else | |
+ var info = GDExtensionClassCreationInfo2 () | |
+ info.create_instance_func = createFunc(_:) | |
+ info.free_instance_func = freeFunc(_:_:) | |
+ info.get_virtual_func = getVirtual | |
+ info.notification_func = notificationFunc2 | |
+ info.recreate_instance_func = recreateFunc | |
+ info.is_exposed = 1 | |
+ | |
+ let retained = Unmanaged<AnyObject>.passRetained(type as AnyObject) | |
+ info.class_userdata = retained.toOpaque() | |
+ | |
+ withUnsafePointer(to: &name.content) { namePtr in | |
+ withUnsafePointer(to: &parent.content) { parentPtr in | |
+ gi.classdb_register_extension_class2 (library, namePtr, parentPtr, &info) | |
+ } | |
+ } | |
+ #endif | |
} | |
/// Registers the user-type specified with the Godot system, and allows it to | |
@@ -229,8 +254,14 @@ public func register<T:Wrapped> (type: T.Type) { | |
/// Currently contains all instantiated objects, but might want to separate those | |
/// (or find a way of easily telling appart) framework objects from user subtypes | |
-var liveFrameworkObjects: [UnsafeRawPointer:Wrapped] = [:] | |
-var liveSubtypedObjects: [UnsafeRawPointer:Wrapped] = [:] | |
+fileprivate var liveFrameworkObjects: [UnsafeRawPointer:Wrapped] = [:] | |
+fileprivate var liveSubtypedObjects: [UnsafeRawPointer:Wrapped] = [:] | |
+ | |
+// Lock for accessing the above | |
+var tableLock = NIOLock() | |
+ | |
+// If not-nil, we are in the process of serially re-creating objects from Godot | |
+fileprivate var bindingObject: UnsafeMutableRawPointer? = nil | |
/// | |
/// Looks into the liveSubtypedObjects table if we have an object registered for it, | |
@@ -239,7 +270,9 @@ var liveSubtypedObjects: [UnsafeRawPointer:Wrapped] = [:] | |
/// The idioms is that we only need to look up subtyped objects, because those | |
/// are the only ones that would keep state | |
func lookupLiveObject (handleAddress: UnsafeRawPointer) -> Wrapped? { | |
- return liveSubtypedObjects [handleAddress] | |
+ tableLock.withLock { | |
+ return liveSubtypedObjects [handleAddress] | |
+ } | |
} | |
/// | |
@@ -249,15 +282,19 @@ func lookupLiveObject (handleAddress: UnsafeRawPointer) -> Wrapped? { | |
/// We are surfacing this, so that when we recreate an object resurfaced in a collection | |
/// we do not get the base type, but the most derived one | |
func lookupFrameworkObject (handleAddress: UnsafeRawPointer) -> Wrapped? { | |
- return liveFrameworkObjects [handleAddress] | |
+ tableLock.withLock { | |
+ return liveFrameworkObjects [handleAddress] | |
+ } | |
} | |
func objectFromHandle (nativeHandle: UnsafeRawPointer) -> Wrapped? { | |
- if let o = (liveFrameworkObjects [nativeHandle] ?? liveSubtypedObjects [nativeHandle]) { | |
- return o | |
+ tableLock.withLock { | |
+ if let o = (liveFrameworkObjects [nativeHandle] ?? liveSubtypedObjects [nativeHandle]) { | |
+ return o | |
+ } | |
+ | |
+ return nil | |
} | |
- | |
- return nil | |
} | |
func lookupObject<T:GodotObject> (nativeHandle: UnsafeRawPointer) -> T? { | |
@@ -293,6 +330,23 @@ func createFunc (_ userData: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointe | |
return UnsafeMutableRawPointer (mutating: o.handle) | |
} | |
+func recreateFunc (_ userData: UnsafeMutableRawPointer?, godotObjecthandle: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? { | |
+ print ("SWIFT: Recreate object userData:\(String(describing: userData))") | |
+ guard let userData else { | |
+ print ("Got a nil userData") | |
+ return nil | |
+ } | |
+ let typeAny = Unmanaged<AnyObject>.fromOpaque(userData).takeUnretainedValue() | |
+ guard let type = typeAny as? Wrapped.Type else { | |
+ print ("SWIFT: The wrapped value did not contain a type: \(typeAny)") | |
+ return nil | |
+ } | |
+ bindingObject = godotObjecthandle | |
+ let o = type.init () | |
+ bindingObject = nil | |
+ return UnsafeMutableRawPointer (mutating: o.handle) | |
+} | |
+ | |
// | |
// This is invoked to release any Subtyped objects we created | |
// | |
@@ -308,11 +362,13 @@ func freeFunc (_ userData: UnsafeMutableRawPointer?, _ objectHandle: UnsafeMutab | |
// #endif | |
if let key = objectHandle { | |
let original = Unmanaged<Wrapped>.fromOpaque(key).takeRetainedValue() | |
- let removed = liveSubtypedObjects.removeValue(forKey: original.handle) | |
- if removed == nil { | |
- print ("SWIFT ERROR: attempt to release object we were not aware of: \(original) \(key)") | |
- } else { | |
- print ("SWIFT: Removed object from our live SubType list (type was: \(original.self)") | |
+ tableLock.withLockVoid { | |
+ let removed = liveSubtypedObjects.removeValue(forKey: original.handle) | |
+ if removed == nil { | |
+ print ("SWIFT ERROR: attempt to release object we were not aware of: \(original) \(key)") | |
+ } else { | |
+ print ("SWIFT: Removed object from our live SubType list (type was: \(original.self)") | |
+ } | |
} | |
} | |
} | |
@@ -321,6 +377,10 @@ func notificationFunc (ptr: UnsafeMutableRawPointer?, code: Int32) { | |
//print ("SWIFT: Notification \(code) on \(ptr)") | |
} | |
+func notificationFunc2 (ptr: UnsafeMutableRawPointer?, code: Int32, reversed: UInt8) { | |
+ //print ("SWIFT: Notification \(code) on \(ptr)") | |
+} | |
+ | |
func userTypeBindingCreate (_ token: UnsafeMutableRawPointer?, _ instance: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? { | |
// Godot-cpp does nothing for user types | |
//print ("SWIFT: instanceBindingCreate") | |
@@ -350,10 +410,12 @@ func frameworkTypeBindingCreate (_ token: UnsafeMutableRawPointer?, _ instance: | |
func frameworkTypeBindingFree (_ token: UnsafeMutableRawPointer?, _ instance: UnsafeMutableRawPointer?, _ binding: UnsafeMutableRawPointer?) { | |
print ("SWIFT: frameworkBindingFree instance=\(String(describing: instance)) binding=\(String(describing: binding)) token=\(String(describing: token))") | |
if let key = instance { | |
- if let removed = liveFrameworkObjects.removeValue(forKey: key) { | |
- print ("SWIFT: Removed from our live Objects with key \(key), removed: \(removed)") | |
- } else { | |
- print ("SWIFT ERROR: attempt to release framework object we were not aware of: \(String(describing: instance))") | |
+ tableLock.withLockVoid { | |
+ if let removed = liveFrameworkObjects.removeValue(forKey: key) { | |
+ print ("SWIFT: Removed from our live Objects with key \(key), removed: \(removed)") | |
+ } else { | |
+ print ("SWIFT ERROR: attempt to release framework object we were not aware of: \(String(describing: instance))") | |
+ } | |
} | |
} | |
if let binding { | |
diff --git a/Sources/SwiftGodot/EntryPoint.swift b/Sources/SwiftGodot/EntryPoint.swift | |
index 19599b8..0501a5d 100644 | |
--- a/Sources/SwiftGodot/EntryPoint.swift | |
+++ b/Sources/SwiftGodot/EntryPoint.swift | |
@@ -74,6 +74,7 @@ struct GodotInterface { | |
let classdb_construct_object: GDExtensionInterfaceClassdbConstructObject | |
let classdb_get_method_bind: GDExtensionInterfaceClassdbGetMethodBind | |
let classdb_register_extension_class: GDExtensionInterfaceClassdbRegisterExtensionClass | |
+ let classdb_register_extension_class2: GDExtensionInterfaceClassdbRegisterExtensionClass2 | |
let classdb_register_extension_class_signal: GDExtensionInterfaceClassdbRegisterExtensionClassSignal | |
let classdb_register_extension_class_method: GDExtensionInterfaceClassdbRegisterExtensionClassMethod | |
let classdb_register_extension_class_property: GDExtensionInterfaceClassdbRegisterExtensionClassProperty | |
@@ -164,6 +165,7 @@ func loadGodotInterface (_ godotGetProcAddrPtr: GDExtensionInterfaceGetProcAddre | |
classdb_construct_object: load ("classdb_construct_object"), | |
classdb_get_method_bind: load ("classdb_get_method_bind"), | |
classdb_register_extension_class: load ("classdb_register_extension_class"), | |
+ classdb_register_extension_class2: load ("classdb_register_extension_class2"), | |
classdb_register_extension_class_signal: load ("classdb_register_extension_class_signal"), | |
classdb_register_extension_class_method: load ("classdb_register_extension_class_method"), | |
classdb_register_extension_class_property: load ("classdb_register_extension_class_property"), |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment