Skip to content

Commit

Permalink
Exposed some previously private things for testing.
Browse files Browse the repository at this point in the history
  • Loading branch information
samdeane committed Dec 19, 2024
1 parent ca17b99 commit 0d82b9c
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 76 deletions.
2 changes: 1 addition & 1 deletion Generator/Generator/UnsafePointerHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func generateUnsafePointerHelpers(_ p: Printer) {
/// }
/// ```
private func generateUnsafeRawPointersN(_ p: Printer, pointerCount count: Int) {
p("struct UnsafeRawPointersN\(count)") {
p("public struct UnsafeRawPointersN\(count)") {
for i in 0..<count {
p("let p\(i): UnsafeRawPointer?")
}
Expand Down
42 changes: 42 additions & 0 deletions Sources/SwiftGodot/Core/Wrapped+Testing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
public struct Testing {
/// Public API for tracking live objects
public struct LiveObjects {
/// All framework objects
public static var framework: [Wrapped] { Array(liveFrameworkObjects.values) }

/// All user-defined objects
public static var subtyped: [Wrapped] { Array(liveSubtypedObjects.values) }

/// All objects
public static var all: [Wrapped] { framework + subtyped }

/// Reset all existing tracked objects
public static func reset() {
liveFrameworkObjects.removeAll()
liveSubtypedObjects.removeAll()
}
}

public struct ClassNames {
public static func setDuplicateNameCallback(_ callback: @escaping (_ name: StringName, _ type: Wrapped.Type) -> Void) -> DuplicateNameCallback {
let old = duplicateClassNameDetected
duplicateClassNameDetected = callback
return old
}
}
}

/// Currently contains all instantiated objects, but might want to separate those
/// (or find a way of easily telling appart) framework objects from user subtypes
internal var liveFrameworkObjects: [UnsafeRawPointer: Wrapped] = [:]
internal var liveSubtypedObjects: [UnsafeRawPointer: Wrapped] = [:]

public typealias DuplicateNameCallback = (StringName, Wrapped.Type) -> Void
var duplicateClassNameDetected: DuplicateNameCallback = { name, type in
preconditionFailure(
"""
Godot already has a class named \(name), so I cannot register \(type) using that name. This is a fatal error because the only way I can tell whether Godot is handing me a pointer to a class I'm responsible for is by checking the class name.
"""
)
}

15 changes: 2 additions & 13 deletions Sources/SwiftGodot/Core/Wrapped.swift
Original file line number Diff line number Diff line change
Expand Up @@ -318,15 +318,6 @@ func bindGodotInstance(instance: some Wrapped, handle: UnsafeRawPointer) {

var userTypes: [String:(UnsafeRawPointer)->Wrapped] = [:]

// @_spi(SwiftGodotTesting) public
var duplicateClassNameDetected: (_ name: StringName, _ type: Wrapped.Type) -> Void = { name, type in
preconditionFailure(
"""
Godot already has a class named \(name), so I cannot register \(type) using that name. This is a fatal error because the only way I can tell whether Godot is handing me a pointer to a class I'm responsible for is by checking the class name.
"""
)
}

func register<T:Wrapped> (type name: StringName, parent: StringName, type: T.Type) {
var nameContent = name.content

Expand Down Expand Up @@ -387,10 +378,7 @@ public func unregister<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] = [:]


// Lock for accessing the above
var tableLock = NIOLock()
Expand Down Expand Up @@ -644,3 +632,4 @@ struct CallableWrapper {
return content
}
}

36 changes: 28 additions & 8 deletions Sources/SwiftGodot/EntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public protocol ExtensionInterface {

}

class LibGodotExtensionInterface: ExtensionInterface {
public class LibGodotExtensionInterface: ExtensionInterface {

/// If your application is crashing due to the Variant leak fixes, please
/// enable this flag, and provide me with a test case, so I can find that
Expand All @@ -33,7 +33,7 @@ class LibGodotExtensionInterface: ExtensionInterface {
private let library: GDExtensionClassLibraryPtr
private let getProcAddrFun: GDExtensionInterfaceGetProcAddress

public init(library: GDExtensionClassLibraryPtr, getProcAddrFun: GDExtensionInterfaceGetProcAddress) {
init(library: GDExtensionClassLibraryPtr, getProcAddrFun: GDExtensionInterfaceGetProcAddress) {
self.library = library
self.getProcAddrFun = getProcAddrFun
}
Expand Down Expand Up @@ -438,12 +438,7 @@ public func initializeSwiftModule(
let initialization = UnsafeMutablePointer<GDExtensionInitialization>(extensionPtr)
initialization.pointee.deinitialize = extension_deinitialize
initialization.pointee.initialize = extension_initialize
#if os(Windows)
typealias RawType = Int32
#else
typealias RawType = UInt32
#endif
initialization.pointee.minimum_initialization_level = GDExtensionInitializationLevel(RawType(minimumInitializationLevel.rawValue))
initialization.pointee.minimum_initialization_level = minimumInitializationLevel.asCType
initialization.pointee.userdata = UnsafeMutableRawPointer(libraryPtr)
}

Expand All @@ -456,3 +451,28 @@ public func initializeSwiftModule(
func withArgPointers(_ _args: UnsafeMutableRawPointer?..., body: ([UnsafeRawPointer?]) -> Void) {
body(unsafeBitCast(_args, to: [UnsafeRawPointer?].self))
}

public func setExtensionInterfaceOpaque(library libraryPtr: UnsafeMutableRawPointer, getProcAddrFun godotGetProcAddr: Any) {
let interface = LibGodotExtensionInterface(library: libraryPtr, getProcAddrFun: godotGetProcAddr as! GDExtensionInterfaceGetProcAddress)
setExtensionInterface(interface: interface)
}



#if os(Windows)
typealias RawType = Int32
#else
typealias RawType = UInt32
#endif

extension GDExtension.InitializationLevel {
var asCType: GDExtensionInitializationLevel {
GDExtensionInitializationLevel(RawType(rawValue))
}
}

extension GDExtensionInitializationLevel {
var asSwiftType: GDExtension.InitializationLevel {
GDExtension.InitializationLevel(rawValue: Int64(exactly: rawValue)!)!
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ extension PhysicsDirectSpaceState2D {
/// The metadata value from the dictionary.
public let metadata: Variant?

init?(_ dictionary: GDictionary) {
public init?(_ dictionary: GDictionary) {
guard dictionary.isEmpty() == false,
let position: Vector2 = dictionary.unwrap(key: "position"),
let normal: Vector2 = dictionary.unwrap(key: "normal"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extension PhysicsDirectSpaceState3D {
/// The face index at the intersection point.
public let faceIndex: Int

init?(_ dictionary: GDictionary) {
public init?(_ dictionary: GDictionary) {
guard dictionary.isEmpty() == false,
let position: Vector3 = dictionary.makeOrUnwrap(key: "position"),
let normal: Vector3 = dictionary.makeOrUnwrap(key: "normal"),
Expand Down
5 changes: 3 additions & 2 deletions Sources/SwiftGodotTestability/GodotRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ private extension GodotRuntime {
guard let godotGetProcAddr, let libraryPtr else {
return 0
}
let interface = LibGodotExtensionInterface(library: libraryPtr, getProcAddrFun: godotGetProcAddr)
setExtensionInterface(interface: interface)
setExtensionInterfaceOpaque(library: libraryPtr, getProcAddrFun: godotGetProcAddr)
godotLibrary = OpaquePointer (libraryPtr)
extensionInit?.pointee = GDExtensionInitialization (
minimum_initialization_level: GDEXTENSION_INITIALIZATION_CORE,
Expand Down Expand Up @@ -113,3 +112,5 @@ private extension GodotRuntime {


}


88 changes: 42 additions & 46 deletions Sources/SwiftGodotTestability/GodotTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
// Created by Mikhail Tishin on 22.10.2023.
//

import XCTest

import SwiftGodot

import XCTest

/// Base class for all test cases that run in the Godot runtime.
open class GodotTestCase: EmbeddedTestCase<GodotTestHost> {
Expand All @@ -35,7 +33,7 @@ open class GodotTestCase: EmbeddedTestCase<GodotTestHost> {
override open func tearDown() async throws {
if GodotRuntime.isRunning {
// clean up test objects
let liveObjects: [Wrapped] = Array(liveFrameworkObjects.values) + Array(liveSubtypedObjects.values)
let liveObjects = Testing.LiveObjects.all
for liveObject in liveObjects {
switch liveObject {
case let node as Node:
Expand All @@ -48,8 +46,7 @@ open class GodotTestCase: EmbeddedTestCase<GodotTestHost> {
print("Unable to free \(liveObject)")
}
}
liveFrameworkObjects.removeAll()
liveSubtypedObjects.removeAll()
Testing.LiveObjects.reset()

// waiting for queueFree to take effect
let scene = try GodotRuntime.getScene()
Expand All @@ -65,78 +62,77 @@ open class GodotTestCase: EmbeddedTestCase<GodotTestHost> {

}

extension GodotTestCase {

public extension GodotTestCase {

/// Asserts approximate equality of two floating point values based on `Math::is_equal_approx` implementation in Godot
func assertApproxEqual<T: FloatingPoint & ExpressibleByFloatLiteral> (_ a: T?, _ b: T?, epsilon: T = 0.00001, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
public func assertApproxEqual<T: FloatingPoint & ExpressibleByFloatLiteral>(_ a: T?, _ b: T?, epsilon: T = 0.00001, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
// Check for exact equality first, required to handle "infinity" values.
guard a != b else { return }
guard let a, let b else {
XCTAssertEqual (a, b, message, file: file, line: line)
XCTAssertEqual(a, b, message, file: file, line: line)
return
}

// Then check for approximate equality.
let tolerance: T = max (epsilon * abs (a), epsilon)
XCTAssertEqual (a, b, accuracy: tolerance, message, file: file, line: line)
let tolerance: T = max(epsilon * abs(a), epsilon)
XCTAssertEqual(a, b, accuracy: tolerance, message, file: file, line: line)
}

/// Asserts approximate equality of two vectors by comparing approximately each component
func assertApproxEqual (_ a: Vector2?, _ b: Vector2?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
public func assertApproxEqual(_ a: Vector2?, _ b: Vector2?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
guard let a, let b else {
XCTAssertEqual (a, b, message, file: file, line: line)
XCTAssertEqual(a, b, message, file: file, line: line)
return
}
assertApproxEqual (a.x, b.x, "Fail due to X. " + message, file: file, line: line)
assertApproxEqual (a.y, b.y, "Fail due to Y. " + message, file: file, line: line)
assertApproxEqual(a.x, b.x, "Fail due to X. " + message, file: file, line: line)
assertApproxEqual(a.y, b.y, "Fail due to Y. " + message, file: file, line: line)
}

/// Asserts approximate equality of two vectors by comparing approximately each component
func assertApproxEqual (_ a: Vector3?, _ b: Vector3?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
public func assertApproxEqual(_ a: Vector3?, _ b: Vector3?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
guard let a, let b else {
XCTAssertEqual (a, b, message, file: file, line: line)
XCTAssertEqual(a, b, message, file: file, line: line)
return
}
assertApproxEqual (a.x, b.x, "Fail due to X. " + message, file: file, line: line)
assertApproxEqual (a.y, b.y, "Fail due to Y. " + message, file: file, line: line)
assertApproxEqual (a.z, b.z, "Fail due to Z. " + message, file: file, line: line)
assertApproxEqual(a.x, b.x, "Fail due to X. " + message, file: file, line: line)
assertApproxEqual(a.y, b.y, "Fail due to Y. " + message, file: file, line: line)
assertApproxEqual(a.z, b.z, "Fail due to Z. " + message, file: file, line: line)
}

/// Asserts approximate equality of two vectors by comparing approximately each component
func assertApproxEqual (_ a: Vector4?, _ b: Vector4?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
public func assertApproxEqual(_ a: Vector4?, _ b: Vector4?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
guard let a, let b else {
XCTAssertEqual (a, b, message, file: file, line: line)
XCTAssertEqual(a, b, message, file: file, line: line)
return
}
assertApproxEqual (a.x, b.x, "Fail due to X. " + message, file: file, line: line)
assertApproxEqual (a.y, b.y, "Fail due to Y. " + message, file: file, line: line)
assertApproxEqual (a.z, b.z, "Fail due to Z. " + message, file: file, line: line)
assertApproxEqual (a.w, b.w, "Fail due to W. " + message, file: file, line: line)
assertApproxEqual(a.x, b.x, "Fail due to X. " + message, file: file, line: line)
assertApproxEqual(a.y, b.y, "Fail due to Y. " + message, file: file, line: line)
assertApproxEqual(a.z, b.z, "Fail due to Z. " + message, file: file, line: line)
assertApproxEqual(a.w, b.w, "Fail due to W. " + message, file: file, line: line)
}

/// Asserts approximate equality of two quaternions by comparing approximately each component
func assertApproxEqual (_ a: Quaternion?, _ b: Quaternion?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
public func assertApproxEqual(_ a: Quaternion?, _ b: Quaternion?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
guard let a, let b else {
XCTAssertEqual (a, b, message, file: file, line: line)
XCTAssertEqual(a, b, message, file: file, line: line)
return
}
assertApproxEqual (a.x, b.x, "Fail due to X. " + message, file: file, line: line)
assertApproxEqual (a.y, b.y, "Fail due to Y. " + message, file: file, line: line)
assertApproxEqual (a.z, b.z, "Fail due to Z. " + message, file: file, line: line)
assertApproxEqual (a.w, b.w, "Fail due to W. " + message, file: file, line: line)
assertApproxEqual(a.x, b.x, "Fail due to X. " + message, file: file, line: line)
assertApproxEqual(a.y, b.y, "Fail due to Y. " + message, file: file, line: line)
assertApproxEqual(a.z, b.z, "Fail due to Z. " + message, file: file, line: line)
assertApproxEqual(a.w, b.w, "Fail due to W. " + message, file: file, line: line)
}

/// Asserts approximate equality of two colors by comparing approximately each component
func assertApproxEqual (_ a: Color?, _ b: Color?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
public func assertApproxEqual(_ a: Color?, _ b: Color?, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
guard let a, let b else {
XCTAssertEqual (a, b, message, file: file, line: line)
XCTAssertEqual(a, b, message, file: file, line: line)
return
}
assertApproxEqual (a.red, b.red, "Fail due to R. " + message, file: file, line: line)
assertApproxEqual (a.green, b.green, "Fail due to G. " + message, file: file, line: line)
assertApproxEqual (a.blue, b.blue, "Fail due to B. " + message, file: file, line: line)
assertApproxEqual (a.alpha, b.alpha, "Fail due to A. " + message, file: file, line: line)
assertApproxEqual(a.red, b.red, "Fail due to R. " + message, file: file, line: line)
assertApproxEqual(a.green, b.green, "Fail due to G. " + message, file: file, line: line)
assertApproxEqual(a.blue, b.blue, "Fail due to B. " + message, file: file, line: line)
assertApproxEqual(a.alpha, b.alpha, "Fail due to A. " + message, file: file, line: line)
}

}
6 changes: 2 additions & 4 deletions Tests/SwiftGodotTests/WrappedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,10 @@ final class DuplicateClassRegistrationTests: GodotTestCase {
register(type: DuplicateClassTestNode.self)
defer { unregister(type: DuplicateClassTestNode.self) }

let old = duplicateClassNameDetected
defer { duplicateClassNameDetected = old }

duplicateClassNameDetected = { [weak self] name, type in
let old = Testing.ClassNames.setDuplicateNameCallback { [weak self] name, type in
self?.duplicateClassNames.append(name)
}
defer { _ = Testing.ClassNames.setDuplicateNameCallback(old) }

register(type: DuplicateClassTestNode.self)

Expand Down

0 comments on commit 0d82b9c

Please sign in to comment.