diff --git a/README.md b/README.md index c8ae4424..9e507120 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A practical interface to the Steamworks SDK using the Swift C++ importer. -**Caveat Integrator: The Swift C++ importer is new and shaky; this package is built on top** +**Caveat Integrator: The Swift C++ importer is new and evolving; this package is built on top** Current state: * All Steamworks interfaces complete - see [API docs](https://johnfairh.github.io/steamworks-swift/index.html) @@ -306,31 +306,35 @@ Fully-fledged AppKit/Metal demo [here](https://github.com/johnfairh/spacewar-swi ### Swift C++ Bugs -_to recheck in Swift 6, noticed 5.10 has fewer simd screw-ups at least_ +_to recheck in Swift 6 for Linux - looking good though_ -Tech limitations, on 5.9 Xcode 15.b6: +Tech limitations, on 6.0 Xcode 16.b3: * Some structures/classes aren't imported -- is the common factor a `protected` destructor? Verify by trying to use `SteamNetworkingMessage_t`. -* Something goes wrong storing pointers to classes and they get nobbled by something. +* ~Something goes wrong storing pointers to classes and they get nobbled by something. Verify by making `SteamIPAddress` a struct and running `TestApiServer`. Or change - interfaces to cache the interface pointers. -* Calls to virtual functions aren't generated properly: Swift generates a ref + interfaces to cache the interface pointers.~ incredibly, fixed in Swift 6 +* ~Calls to virtual functions aren't generated properly: Swift generates a ref to a symbol instead of doing the vtable call. So the actual C++ interfaces are not - usable in practice. Will use the flat API. + usable in practice. Will use the flat API.~ allegedly fixed in Swift 6 but don't + need due to history. * Anonymous enums are not imported at all. Affects callback etc. ID constants. Will work around. * ~sourcekit won't give me a module interface for `CSteamworks` to see what else the importer is doing. Probably Xcode's fault, still not passing the user's flags to sourcekit and still doing insultingly bad error-reporting.~ fixed in Xcode 15?! * Linux only: random parts of Glibc silently fail to import. SMH. Work around in C++. + See `swift_shims.h`. * ~Linux only: implicit struct constructors are not created, Swift generates a ref to a non-existent method that fails at link time. Work around with dumb C++ allocate shim.~ Sort of fixed in 5.9, but instead `swiftc` crashes on some uses -- on - both macOS and Linux. Check by refs to eg. `CSteamNetworkingIPAddr_Allocate()`.` + both macOS and Linux. Check by refs to eg. `CSteamNetworkingIPAddr_Allocate()`, see + `steam_missing.h`. * Linux only, _again_: SPM test auto-discovery has no clue about C++ interop. Work around by smashing in the flag everywhere... -* Swift 5.8+ adopts a broken/paranoid model about 'projected pointers' requiring some fairly - ugly code to work around. Verify with the `__ unsafe` stuff in `ManualTypes.swift`. +* ~Swift 5.8+ adopts a broken/paranoid model about 'projected pointers' requiring some fairly + ugly code to work around. Verify with the `__ unsafe` stuff in `ManualTypes.swift`.~ + fixed by Swift 6ish ### Non-Swift Problems diff --git a/Sources/CSteamworks/steam_matchmaking_shims.h b/Sources/CSteamworks/steam_matchmaking_shims.h index 4737ee19..4e84cc58 100644 --- a/Sources/CSteamworks/steam_matchmaking_shims.h +++ b/Sources/CSteamworks/steam_matchmaking_shims.h @@ -55,8 +55,9 @@ class CShimServerListResponse: ISteamMatchmakingServerListResponse { // Help poor confused Swift get hold of the superclass interface. // Swift 5.8: It's, somehow, even more confused: the member version of this no longer works // Swift 5.9: Still borked - member version appears as an '__.Unsafe' monstrosity. - static _Nonnull ISteamMatchmakingServerListResponse *GetSteamInterface(_Nonnull CShimServerListResponse *thiz) { - return thiz; + // Swift 6.0: Back to the 5.7 level of confusion. hooray? + _Nonnull ISteamMatchmakingServerListResponse *GetSteamInterface() { + return this; } // Overrides - proxy to vtable @@ -89,8 +90,8 @@ class CShimPingResponse: ISteamMatchmakingPingResponse { delete this; } - static _Nonnull ISteamMatchmakingPingResponse *GetSteamInterface(_Nonnull CShimPingResponse *thiz) { - return thiz; + _Nonnull ISteamMatchmakingPingResponse *GetSteamInterface() { + return this; } // Steamworks doesn't put this in the callbacks so we have to stash it in order @@ -126,8 +127,8 @@ class CShimPlayersResponse: ISteamMatchmakingPlayersResponse { delete this; } - static _Nonnull ISteamMatchmakingPlayersResponse *GetSteamInterface(_Nonnull CShimPlayersResponse *thiz) { - return thiz; + _Nonnull ISteamMatchmakingPlayersResponse *GetSteamInterface() { + return this; } HServerQuery handle; @@ -164,11 +165,10 @@ class CShimRulesResponse: ISteamMatchmakingRulesResponse { delete this; } - static _Nonnull ISteamMatchmakingRulesResponse *GetSteamInterface(_Nonnull CShimRulesResponse *thiz) { - return thiz; + _Nonnull ISteamMatchmakingRulesResponse *GetSteamInterface() { + return this; } - HServerQuery handle; // Overrides diff --git a/Sources/CSteamworks/steamapi_headers.h b/Sources/CSteamworks/steamapi_headers.h index 5ebfdc93..0f05f02c 100644 --- a/Sources/CSteamworks/steamapi_headers.h +++ b/Sources/CSteamworks/steamapi_headers.h @@ -1,9 +1,6 @@ #include #include -//// Steamworks 1.59 still has fucked up SteamVideo json -//S_API ISteamVideo *SteamAPI_SteamVideo_v004(); - #include #include #include "Generated/steam_struct_shims.h" diff --git a/Sources/Steamworks/ManualInterfaces.swift b/Sources/Steamworks/ManualInterfaces.swift index a074a858..722490ce 100644 --- a/Sources/Steamworks/ManualInterfaces.swift +++ b/Sources/Steamworks/ManualInterfaces.swift @@ -139,7 +139,7 @@ extension SteamNetworkingUtils { public func useLoggerForDebug(detailLevel: SteamNetworkingSocketsDebugOutputType) { SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction(interface, ESteamNetworkingSocketsDebugOutputType(detailLevel), - // XXX swift 6 + // XXX swift 6 - fixed in Beta 3, need CI to update { networkingUtilsDebugCallback(type: $0, msg: $1) } ) } } diff --git a/Sources/Steamworks/ManualTypes.swift b/Sources/Steamworks/ManualTypes.swift index d96b28e9..a3f44dd0 100644 --- a/Sources/Steamworks/ManualTypes.swift +++ b/Sources/Steamworks/ManualTypes.swift @@ -25,11 +25,8 @@ import Darwin // A very rough manual wrap to work around the union implementation // and expose the static getters. -// if we make `SteamIPAddress` a `struct` as it should be then the -// embedded `SteamIPAddress_t` gets nobbled somehow during runtime... - /// Steamworks `SteamIPAddress_t` -public final class SteamIPAddress { +public struct SteamIPAddress { private let ip: SteamIPAddress_t // MARK: Properties @@ -164,7 +161,6 @@ extension SteamInputActionEvent: SteamCreatable {} // MARK: servernetadr // Some more bizarreness from MMS. What even is `SteamIPAddress`... -// Again we have to be `final class` to avoid the weird corruption of the embedded C++ thing. // // This is read-only type afaics so just port the getters. // None of them are const-correct... @@ -173,23 +169,8 @@ extension SteamInputActionEvent: SteamCreatable {} // // Can't implement comparable because Swift C++ import doesn't understand C++ operator overloads. -#if $NewCxxMethodSafetyHeuristics -#else -/// Swift 5.8+ workarounds for C++ importer being dumb -/// (though in this case the C++ implementation is shockingly unsafe for utterly non-C++ reasons) -extension servernetadr_t { - func GetConnectionAddressString() -> String { - String(__GetConnectionAddressStringUnsafe()) - } - - func GetQueryAddressString() -> String { - String(__GetQueryAddressStringUnsafe()) - } -} -#endif - /// Steamworks `servernetadr` -public final class ServerNetAdr: Sendable { +public struct ServerNetAdr: Sendable { private let adr: servernetadr_t init(_ steam: servernetadr_t) { @@ -229,7 +210,7 @@ extension ServerNetAdr: SteamCreatable {} // that users can instantiate. /// Steamworks `SteamNetworkingIPAddr` -public final class SteamNetworkingIPAddr: @unchecked Sendable { +public struct SteamNetworkingIPAddr: @unchecked Sendable { typealias SteamType = CSteamworks.SteamNetworkingIPAddr let adr: SteamType @@ -292,7 +273,7 @@ public final class SteamNetworkingIPAddr: @unchecked Sendable { } /// `INADDR_ANY` with some port - public convenience init(inaddrAnyPort port: UInt16) { + public init(inaddrAnyPort port: UInt16) { var adr = SteamType() adr.Clear() adr.m_port = port @@ -300,7 +281,7 @@ public final class SteamNetworkingIPAddr: @unchecked Sendable { } /// Sets to IPv4 mapped address. IP and port are in host byte order. - public convenience init(ipv4: Int, port: UInt16) { + public init(ipv4: Int, port: UInt16) { var adr = SteamType() adr.SetIPv4(UInt32(ipv4), port) self.init(adr) @@ -308,14 +289,14 @@ public final class SteamNetworkingIPAddr: @unchecked Sendable { /// IP is interpreted as bytes, so there are no endian issues. (Same as `inaddr_in6`.) /// The IP can be a mapped IPv4 address. - public convenience init(ipv6: [UInt8], port: UInt16) { + public init(ipv6: [UInt8], port: UInt16) { var adr = SteamType() adr.SetIPv6(ipv6, port) self.init(adr) } /// Parse an IP address and optional port. If a port is not present, it is set to 0. - public convenience init?(addressAndPort: String) { + public init?(addressAndPort: String) { var adr = SteamType() adr.Clear() guard adr.ParseString(addressAndPort) else { @@ -346,30 +327,10 @@ extension CSteamworks.SteamNetworkingIPAddr { // MARK: SteamNetworkingIdentity // Again model as immutable thing that can be created. -// Can't quite `enum`-ify this because: -// 1) The random non-class-corruption issue; -// 2) The 'types' enum is large and weird. - -#if $NewCxxMethodSafetyHeuristics -#else -/// Swift 5.8 workarounds for the C++ importer trying to be clever but ending up being really dumb -extension CSteamworks.SteamNetworkingIdentity { - func GetIPAddr() -> UnsafePointer! { - __GetIPAddrUnsafe() - } - - func GetGenericString() -> UnsafePointer! { - __GetGenericStringUnsafe() - } - - func GetGenericBytes(_ cbLen: inout int32) -> UnsafePointer! { - __GetGenericBytesUnsafe(&cbLen) - } -} -#endif +// Can't quite `enum`-ify this because the 'types' enum is large and weird. /// Steamworks `SteamNetworkingIdentity` -public final class SteamNetworkingIdentity: @unchecked Sendable { +public struct SteamNetworkingIdentity: Sendable { typealias SteamType = CSteamworks.SteamNetworkingIdentity let identity: SteamType @@ -450,14 +411,14 @@ public final class SteamNetworkingIdentity: @unchecked Sendable { } /// Init from a Steam ID - public convenience init(_ steamID: SteamID) { + public init(_ steamID: SteamID) { var identity = SteamType() identity.SetSteamID64(steamID.asUInt64) // also sets type self.init(identity) } /// Init from an IP address - public convenience init(_ ipaddr: SteamNetworkingIPAddr) { + public init(_ ipaddr: SteamNetworkingIPAddr) { var identity = SteamType() identity.SetIPAddr(ipaddr.adr) // also sets type self.init(identity) @@ -471,7 +432,7 @@ public final class SteamNetworkingIdentity: @unchecked Sendable { } /// Init generic string or some other type. Max length 31 bytes. - public convenience init?(genericString: String, type: SteamNetworkingIdentityType = .genericString) { + public init?(genericString: String, type: SteamNetworkingIdentityType = .genericString) { var identity = SteamType() guard identity.SetGenericString(genericString) else { return nil @@ -481,7 +442,7 @@ public final class SteamNetworkingIdentity: @unchecked Sendable { } /// Init from a `description` string. - public convenience init?(description: String) { + public init?(description: String) { var identity = SteamType() guard identity.ParseString(description) else { return nil @@ -490,7 +451,7 @@ public final class SteamNetworkingIdentity: @unchecked Sendable { } /// Init generic bytes or some other type. Max 32 bytes. - public convenience init?(_ bytes: [UInt8], type: SteamNetworkingIdentityType = .genericBytes) { + public init?(_ bytes: [UInt8], type: SteamNetworkingIdentityType = .genericBytes) { var identity = SteamType() guard identity.SetGenericBytes(bytes, bytes.count) else { return nil @@ -649,6 +610,7 @@ extension SteamNetworkingMessage: SteamCreatable { // MARK: SteamNetworkingConfigValue // Another enum-y union thing, used for writing to SteamNetworking +// (This is intentionally a class to get a deinit for the owned storage...) /// Steamworks `SteamNetworkingConfigValue_t` public final class SteamNetworkingConfigValue: @unchecked Sendable { @@ -719,3 +681,4 @@ extension AppID { /// The well-known _SpaceWar_ ``AppID`` public static let spaceWar = Self(480) } + diff --git a/Sources/Steamworks/SteamMatchmakingServers+Manual.swift b/Sources/Steamworks/SteamMatchmakingServers+Manual.swift index b4a92959..2ec2e37b 100644 --- a/Sources/Steamworks/SteamMatchmakingServers+Manual.swift +++ b/Sources/Steamworks/SteamMatchmakingServers+Manual.swift @@ -108,7 +108,7 @@ extension SteamMatchmakingServers { AppId_t(appIndex), .init(tmp_filters), uint32(filters.count), - CShimServerListResponse.GetSteamInterface(shim))) + shim.pointee.GetSteamInterface())) guard rc != .invalid else { shim.pointee.Deallocate() return .invalid @@ -196,7 +196,7 @@ extension SteamMatchmakingServers { let rc = HServerQuery(SteamAPI_ISteamMatchmakingServers_PingServer(interface, UInt32(ip), port, - CShimPingResponse.GetSteamInterface(shim))) + shim.pointee.GetSteamInterface())) guard rc != .invalid else { shim.pointee.Deallocate() return .invalid @@ -212,7 +212,7 @@ extension SteamMatchmakingServers { let rc = HServerQuery(SteamAPI_ISteamMatchmakingServers_PlayerDetails(interface, UInt32(ip), port, - CShimPlayersResponse.GetSteamInterface(shim))) + shim.pointee.GetSteamInterface())) guard rc != .invalid else { shim.pointee.Deallocate() return .invalid @@ -228,7 +228,7 @@ extension SteamMatchmakingServers { let rc = HServerQuery(SteamAPI_ISteamMatchmakingServers_ServerRules(interface, UInt32(ip), port, - CShimRulesResponse.GetSteamInterface(shim))) + shim.pointee.GetSteamInterface())) guard rc != .invalid else { shim.pointee.Deallocate() return .invalid diff --git a/Tests/SteamworksTests/TestApiServer.swift b/Tests/SteamworksTests/TestApiServer.swift index 08b9a022..a67f2170 100644 --- a/Tests/SteamworksTests/TestApiServer.swift +++ b/Tests/SteamworksTests/TestApiServer.swift @@ -31,7 +31,7 @@ class TestApiServer: XCTestCase { client.runCallbacks() } - override func tearDown() { - _ = try? TestClient.recycleClient() - } +// override func tearDown() { +// _ = try? TestClient.recycleClient() +// } } diff --git a/steamworks-swift.xcodeproj/project.pbxproj b/steamworks-swift.xcodeproj/project.pbxproj index 6f238b11..d2615db6 100644 --- a/steamworks-swift.xcodeproj/project.pbxproj +++ b/steamworks-swift.xcodeproj/project.pbxproj @@ -407,6 +407,7 @@ 02B81B202886C82400780A57 /* SteamMatchmaking+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SteamMatchmaking+Helpers.swift"; sourceTree = ""; }; 02B81B232886D68B00780A57 /* SteamUser+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SteamUser+Helpers.swift"; sourceTree = ""; }; 02BD4F5A2C3D43B100A50860 /* SteamTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteamTimeline.swift; sourceTree = ""; }; + 02BD4F5C2C3D63D700A50860 /* swift_shims.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = swift_shims.h; sourceTree = ""; }; 02D309D62722C49B00DE64F0 /* SteamAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteamAPI.swift; sourceTree = ""; }; 02D309DD2722D81E00DE64F0 /* SteamBaseAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteamBaseAPI.swift; sourceTree = ""; }; 02D309E22728111A00DE64F0 /* Lock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = ""; }; @@ -760,6 +761,7 @@ OBJ_8 /* CSteamworks */ = { isa = PBXGroup; children = ( + 02BD4F5C2C3D63D700A50860 /* swift_shims.h */, OBJ_9 /* steamapi_headers.h */, 02AD45712751565E00BD93ED /* steam_missing.h */, 02AD45EF2758F01100BD93ED /* steam_matchmaking_shims.h */,