Skip to content

Commit

Permalink
Update Swift C++ workarounds for Swift 6
Browse files Browse the repository at this point in the history
  • Loading branch information
johnfairh committed Jul 9, 2024
1 parent 3be4654 commit 633531c
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 83 deletions.
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
18 changes: 9 additions & 9 deletions Sources/CSteamworks/steam_matchmaking_shims.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions Sources/CSteamworks/steamapi_headers.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#include <steam/steam_api.h>
#include <steam/steam_gameserver.h>

//// Steamworks 1.59 still has fucked up SteamVideo json
//S_API ISteamVideo *SteamAPI_SteamVideo_v004();

#include <steam/steam_api_flat.h>
#include <steam/steamnetworkingfakeip.h>
#include "Generated/steam_struct_shims.h"
Expand Down
2 changes: 1 addition & 1 deletion Sources/Steamworks/ManualInterfaces.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) } )
}
}
Expand Down
69 changes: 16 additions & 53 deletions Sources/Steamworks/ManualTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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...
Expand All @@ -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) {
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -292,30 +273,30 @@ 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
self.init(adr)
}

/// 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)
}

/// 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 {
Expand Down Expand Up @@ -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<CSteamworks.SteamNetworkingIPAddr>! {
__GetIPAddrUnsafe()
}

func GetGenericString() -> UnsafePointer<CChar>! {
__GetGenericStringUnsafe()
}

func GetGenericBytes(_ cbLen: inout int32) -> UnsafePointer<uint8>! {
__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

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -719,3 +681,4 @@ extension AppID {
/// The well-known _SpaceWar_ ``AppID``
public static let spaceWar = Self(480)
}

8 changes: 4 additions & 4 deletions Sources/Steamworks/SteamMatchmakingServers+Manual.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions Tests/SteamworksTests/TestApiServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class TestApiServer: XCTestCase {
client.runCallbacks()
}

override func tearDown() {
_ = try? TestClient.recycleClient()
}
// override func tearDown() {
// _ = try? TestClient.recycleClient()
// }
}
2 changes: 2 additions & 0 deletions steamworks-swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@
02B81B202886C82400780A57 /* SteamMatchmaking+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SteamMatchmaking+Helpers.swift"; sourceTree = "<group>"; };
02B81B232886D68B00780A57 /* SteamUser+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SteamUser+Helpers.swift"; sourceTree = "<group>"; };
02BD4F5A2C3D43B100A50860 /* SteamTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteamTimeline.swift; sourceTree = "<group>"; };
02BD4F5C2C3D63D700A50860 /* swift_shims.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = swift_shims.h; sourceTree = "<group>"; };
02D309D62722C49B00DE64F0 /* SteamAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteamAPI.swift; sourceTree = "<group>"; };
02D309DD2722D81E00DE64F0 /* SteamBaseAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteamBaseAPI.swift; sourceTree = "<group>"; };
02D309E22728111A00DE64F0 /* Lock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down

0 comments on commit 633531c

Please sign in to comment.