migueldeicaza / SwiftGodot

New Godot bindings for Swift
https://migueldeicaza.github.io/SwiftGodotDocs/tutorials/swiftgodot-tutorials/
MIT License
985 stars 55 forks source link

Issues with multiple extensions in the same project #463

Closed rktprof closed 1 month ago

rktprof commented 2 months ago

I'm experiencing some strange things with having multiple SwiftGodot gdextensions in the same project

Godot 4.2.2 SwiftGodot 0.41.0

Both of the extensions, libGameCenter.dylib and libNetworking.dylib works when they are the only extension available. When I add the Networking extension the GameCenter extension is no longer accessible in the project and I can no longer access any of the classes.

The only error I get is

E 0:00:00:0509   _register_extension_class_internal: Attempt to register extension class 'NetworkBrowser', which appears to be already registered.
  <C++ Error>    Condition "ClassDB::class_exists(class_name)" is true.
  <C++ Source>   core/extension/gdextension.cpp:386 @ _register_extension_class_internal() 
E 0:00:00:0509   _register_extension_class_internal: Attempt to register extension class 'NetworkListener', which appears to be already registered.
  <C++ Error>    Condition "ClassDB::class_exists(class_name)" is true.
  <C++ Source>   core/extension/gdextension.cpp:386 @ _register_extension_class_internal()

NetworkBrowser & NetworkListener are the two classes defined in #initSwiftExtension for the Networking extension

Additionally, I think godot messes up some caching because if I remove the gamecenter.gdextension file (but keep libGameCenter.dylib in the folder) and run the game I get the same errors, but now the extension works

migueldeicaza commented 2 months ago

Can you share the initSwiftExtension calls you are using?

rktprof commented 2 months ago

Can you share the initSwiftExtension calls you are using?

#initSwiftExtension(cdecl: "networking_init", types: [
    LocalNetworkListener.self,
    LocalNetworkDiscovery.self,
    ])

(I renamed the Networking ones since initially posting this issue)

#initSwiftExtension(cdecl: "gamecenter_init", types: [
    GameCenter.self,
    GameCenterFriends.self,
    GameCenterLeaderboards.self,
    GameCenterAchievements.self,
    GameCenterMatchmaking.self,
    GameCenterPlayer.self,
    GameCenterFriend.self,
    LeaderboardPlayer.self,
])
rktprof commented 1 month ago

So I've done some more testing with regards to this and it always breaks as soon as I add two extensions, this is running Godot 2.2 and the latest main branch of SwiftGodot

I created a new empty godot project and two minimal extensions

Godot project:

main.gd ```gdscript extends Node func _ready() -> void: print("Extension 1 present: %s" % ClassDB.class_exists("Extension1Class")) print("Extension 2 present: %s" % ClassDB.class_exists("Extension2Class")) ```
bin/extension1.gdextension ```ini [configuration] entry_symbol = "extension_init" compatibility_minimum = 4.2 [libraries] macos.debug = "res://bin/libExtension1.dylib" [dependencies] macos.debug = {"res://bin/libSwiftGodot.dylib" : ""} ```
bin/extension2.gdextension ```ini [configuration] entry_symbol = "extension_init" compatibility_minimum = 4.2 [libraries] macos.debug = "res://bin/libExtension2.dylib" [dependencies] macos.debug = {"res://bin/libSwiftGodot.dylib" : ""} ```

Then I created two extensions

Extension1:

Package.swift ```swift // swift-tools-version: 5.10 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "Extension1", platforms: [ .macOS(.v13) ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "Extension1", type: .dynamic, targets: ["Extension1"] ), ], dependencies: [ .package(url: "https://github.com/migueldeicaza/SwiftGodot", branch: "main") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( name: "Extension1", dependencies: ["SwiftGodot"], swiftSettings: [.unsafeFlags(["-suppress-warnings"])] ), ] ) ```
Extension1.swift ```swift import SwiftGodot #initSwiftExtension(cdecl: "extension_init", types: [Extension1Class.self]) ```
Extension1Class.swift ```swift import SwiftGodot class Extension1Class:RefCounted { } ```

Extension2:

Package.swift ```swift // swift-tools-version: 5.10 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "Extension2", platforms: [ .macOS(.v13) ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "Extension2", type: .dynamic, targets: ["Extension2"] ), ], dependencies: [ .package(url: "https://github.com/migueldeicaza/SwiftGodot", branch: "main") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( name: "Extension2", dependencies: ["SwiftGodot"], swiftSettings: [.unsafeFlags(["-suppress-warnings"])] ), ] ) ```
Extension2.swift ```swift import SwiftGodot #initSwiftExtension(cdecl: "extension_init", types: [Extension2Class.self]) ```
Extension2Class.swift ```swift import SwiftGodot class Extension2Class:Object { } ```

And when I run the project the log output is

Godot Engine v4.2.2.stable.official.15073afe3 - https://godotengine.org
Vulkan API 1.2.231 - Forward Mobile - Using Vulkan Device #0: Apple - Apple M1 Max

Extension 1 present: false
Extension 2 present: true

and an error:

_register_extension_class_internal: Attempt to register extension class 'Extension2Class', which appears to be already registered.
  <C++ Error>    Condition "ClassDB::class_exists(class_name)" is true.
  <C++ Source>   core/extension/gdextension.cpp:386 @ _register_extension_class_internal()

Here are all the files project.zip

migueldeicaza commented 1 month ago

The challenge is a little more complex than I hoped, see this bug for details

https://github.com/migueldeicaza/SwiftGodot/issues/72

migueldeicaza commented 1 month ago

The initial issue was that multiple extension overwrote the global that kept track of the init functions, and we would only ever initialize one. The fix attempt added them to an array, but Godot would call the init function, once per library, and then we would attempt registration twice, which would produce an ugly error.

The fix in 81e92c5fed8ba2a5c4a01be9bdb923758aa9f07d should fix both.

Thank you for the reproducible test case.