Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Swift vs. C++ workarounds #15

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading