A practical interface to the Steamworks SDK using the Swift C++ importer

Related tags

Database steam-swift
Overview

macOS Steamworks 1.54 MIT

steamworks-swift

A practical interface to the Steamworks SDK using the Swift C++ importer.

Caveat Integrator: The Swift C++ importer is a chaotic science project; this package is built on top

Current state:

  • All Steamworks interfaces complete - see rough docs
  • Code gen creates Swift versions of Steam types; callbacks and call-returns work
  • Some interface quality-of-life helpers in a separate SteamworksHelpers module
  • make test builds and runs unit tests that run frame loops and access portions of the Steam API doing various sync and async tasks.
  • Encrypted app ticket support in separate SteamworksEncryptedAppTicket module
  • Separate demo showing encrypted app-ticket stuff, make run_ticket
  • Requires Swift 5.7, Xcode 14 beta 4
  • The Xcode project basically works, assumes sdk exists. SourceKit can manage tab completion even if module interface gen is beyond it
  • Can't get anything out of SteamInput so can't tell if the translation is reasonable :/

Concept

  • Offer a pure Swift module Steamworks covering all of the current Steamworks API
  • Leave out the deprecated and WIN32-only stuff
  • Do not diverge too far from the 'real' API names to aid docs / searching / porting: I think this is a better starting point than doing a complete OO analysis to carve out function. Can go to build SteamworksPatterns or something if worthwhile. Name etc. changes:
    • Don't use Swift properties for 0-arg getters: diverges too far from Steamworks naming
    • Drop the intermittent Hungarian notation (argh the 1990s are calling)
    • Use Swift closures for callbacks as well as async-await sugar
    • Map unions onto enums with associated values
  • Provide custom API-lifetime and message dispatch classes
  • Provide strongly typed handles
  • Access interfaces via central types
  • Use code gen to deal with the ~900 APIs and their ~400 types, taking advantage of the handy JSON file. This code-gen piece is the actual main work in this project
  • Provide quality-of-life helpers module SteamworksHelpers to wrap up API patterns involving multiple calls, usually determining buffer lengths

Next

  • Try generating a DocC package, using code-gen to make a sensible layout
  • Port Spacewar over to Swift

API mapping design

Lifecycle

// Initialization
let steam = SteamAPI(appID: MyAppId) // or `SteamGameServerAPI`

// Frame loop
steam.runCallbacks() // or `steam.releaseCurrentThreadMemory()`

// Shutdown
// ...when `steam` goes out of scope

Callbacks

C++

STEAM_CALLBACK(MyClass, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived);

...

m_CallbackUserStatsReceived( this, &MyClass::OnUserStatsReceived )

...

void MyClass::OnUserStatsReceived( UserStatsReceived_t *pCallback ) {
  ...
}

Swift

steam.onUserStatsReceived { userStatsReceived in
  ...
}

There are async versions too, like:

for await userStatsReceived in steam.userStatsReceived {
  ...
}

...but these need the panacea of custom executors to be practical.

Functions

auto handle = SteamInventory()->StartUpdateProperties();
let handle = steam.inventory.startUpdateProperties()

Call-return style

C++

CCallResult m_GetFollowerCountCallResult;

...

auto hSteamAPICall = SteamFriends.GetFollowerCount(steamID);
m_GetFollowerCountCallResult.Set(hSteamAPICall, this, &MyClass::OnGetFollowerCount);

...

void MyClass::OnGetFollowerCount(FriendsGetFollowerCount_t *pCallback, bool bIOFailure) {
  ...
}

Swift

steam.friends.getFollowerCount(steamID: steamID) { getFollowerCount in
  guard let getFollowerCount = getFollowerCount else {
    // `bIOFailure` case
    ...
  }
  ...
}

Again there are async versions that are impractical for now:

let getFollowerCount = await steam.friends.getFollowerCount(steamID: steamID)

Array-length parameters

Parameters carrying the length of an input array are discarded because Swift arrays carry their length with them.

'Out' parameters

C++ 'out' parameters filled in by APIs are returned in a tuple, or, if the Steam API is void then as the sole return value.

SteamInventoryResult_t result;
bool rc = SteamInventory()->GrantPromoItems(&result);
let (rc, result) = steamAPI.inventory.grantPromoItems()

Optional 'out' parameters

Some C++ 'out' parameters are optional: they can be passed as NULL to indicate they're not required by caller. In the Swift API these generate an additional boolean parameter return with default true.

auto avail = SteamNetworkingUtils()->GetRelayNetworkStatusAvailability(NULL);
let (avail, _) = steamAPI.networkingUtils.getRelayNetworkStatusAvailability(returnDetails: false)

The return tuple is still populated with something but its contents is undefined; the library guarantees to pass NULL to the underlying Steamworks API.

'In-out' parameters

C++ parameters whose values are significant and also have their value updated are present in both Swift function parameters and the return tuple.

uint32 itemDefIDCount = 0;
bool rc1 = SteamInventory()->GetItemDefinitionIDs(NULL, &itemDefIDCount);
auto itemDefIDs = new SteamItemDef_t [itemDefIDCount];
bool rc2 = SteamInventory()->GetItemDefinitions(itemDefIDs, &itemDefIDCount);
let (rc1, _, itemDefIDCount) = steamAPI.inventory.
                                   getItemDefinitionIDs(returnItemDefIDs: false,
                                                        itemDefIDsArraySize: 0)
let (rc2, itemDefIDs, _) = steamAPI.inventory.
                               getItemDefinitionIDs(itemDefIDsArraySize: itemDefIDCount)

Default parameter values

Default values are provided where the API docs suggest a value, but there are still APIs where caller is required to provide a max buffer length for an output string -- these look pretty weird in Swift but no way to avoid. Some Steamworks APIs support the old "pass NULL to get the required length" two-pass style and these patterns are wrapped up in a Swifty way in the SteamworksHelpers module.

Swift C++ Bugs

Tech limitations, on 5.7 Xcode 14.0b3:

  • Have to manually tell Swift to link with libc++. Verify by commenting from Makefile. When resolved tidy Makefile. currently fixed in 5.7
  • Importing Dispatch and -enable-cxx-interop makes DispatchSemaphore disappear but not the rest of the module?? Work around. When resolved rewrite mutex. currently fixed in 5.7
  • Some structures/classes aren't imported -- the common factor seems to be a protected destructor. Verify by trying to use SteamNetworkingMessage_t.
  • Something goes wrong storing pointers to classes and they get nobbled by something. Verify by making SteamIPAddress a struct, changing interfaces to cache the interface pointers.
  • Some C++ types with operator == don't have Equatable generated. Verify with SteamNetworkingIPAddr. Got worse in 5.7
  • Importing Foundation and -enable-cxx-interop and a C++ module goes wrong. Swift 5.6 doesn't crash; worse the compiler goes slow, spits out warnings, then the binary runs like treacle. Will aim to not depend on Foundation, see how that goes. seems fixed in 5.7 but build is really slow - keep up not using Foundation?
  • 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.
  • 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.

Non-Swift Problems

  • Some Steamworks SDK issues, nothing too serious.
  • CI really needs a private runner with a logged-in steam account, current version just runs the non-steam-requiring tests.

Weird Steam messages

Getting unexpected SteamAPICallCompleteds out of SteamAPI_ManualDispatch_GetNextCallback() -- suspect parts of steamworks trying to use callbacks internally without understanding manual dispatch mode. Or I'm missing an API somewhere to dispatch them.

  • 2101 - HTTPRequestCompleted_t.k_iCallback
  • 1296 - k_iSteamNetworkingUtilsCallbacks + 16 - undefined, not a clue

Seems triggered by using steamnetworking.

Facepunch logs & drops these too, so, erm, shrug I suppose.

Getting src/steamnetworkingsockets/clientlib/csteamnetworkingmessages.cpp (229) : Assertion Failed: [#40725897 pipe] Unlinking connection in state 1 using steamnetworkingmessages; possibly it's not expecting to send messages from a steam ID to itself.

Requirements

  • Needs Swift 5.7 (Xcode 14 beta)
  • Needs Steam client installed (and logged-in, running for the tests)
  • I'm using macOS 12; should work on macOS 11, Linux; might work on Windows

Interface plan

ISteamAppList, ISteamApps, ISteamFriends, ISteamGameSearch, ISteamGameServer, ISteamGameServerStats, ISteamHTMLSruface, ISteamHTTP, ISteamInput, ISteamInventory, ISteamMatchMaking, ISteamMatchmakingServers, ISteamMusic, ISteamMusicRemote, ISteamNetworkingMessages, ISteamNetworkingSockets, ISteamNetworkingUtils, ISteamParentalSettings, ISteamParties, ISteamRemotePlay, ISteamRemoteStorage, ISteamScreenshots, ISteamUGC, ISteamUser, ISteamUserStats, ISteamUtils, ISteamVideo, SteamEncryptedAppTicket

Skip:

  • ISteamAppTicket - er not actually a thing?
  • ISteamClient - internal stuff, very C++y, looks ignorable
  • ISteamController - deprecated, need to review for stuff that's been generated and can actually be deleted after working through ISteamInput
  • ISteamGameCoordinator - "largely" deprecated
  • ISteamNetworking - this is the pre-modern interface
  • ISteamPS3... - will leave Swift-on-PS3 for another day

JSON notes

Capture some notes on troubles reflecting the json into the module.

  • The 'modern' isteamnetworking stuff is incomplete somehow - Json describes SteamDatagramGameCoordinatorServerLogin, SteamDatagramHostedAddress are missing from the header files. The online API docs are hilariously broken here, scads of broken links. Have to wait for Valve to fix this.

    I found some of this in the SDR SDK, but it's not supported on macOS and uses actual grown-up C++ with std::string and friends so best leave it alone for now.

  • SteamNetworkingMessage_t doesn't import into Swift. Probably stumbling into a hole of C++ struct with function pointer fields. Trust Apple will get to this eventually, will write a zero-cost inline shim.

  • Json (and all non-C languages) struggles with unions. Thankfully rare: SteamIPAddress_t, SteamInputAction_t, SteamNetworkingConfigValue_t. SteamNetworkingConfigValue_t. Rare enough to deal with manually.

  • Loads of missing out_string_count etc. annotations and a few wrong, see patchfile.

Contributions

Welcome: open an issue / [email protected] / @johnfairh

License

Distributed under the MIT license. Except the Steamworks SDK parts.

You might also like...
Implement Student Admission System using SQlite
Implement Student Admission System using SQlite

StudentAdmissionSQLiteApp Implement Student Admission System using SQlite. #Func

Creating a Todo app using Realm and SwiftUI
Creating a Todo app using Realm and SwiftUI

Realmで作るTodoアプリ note記事「【SwiftUI】Realmを使ってTodoアプリを作る」のソースです。

Innova CatchKennyGame - The Image Tap Fun Game with keep your scores using Core Database
Innova CatchKennyGame - The Image Tap Fun Game with keep your scores using Core Database

Innova_CatchKennyGame The Image Tap Fun Game with keep your scores using Core Da

BowTies - The main purpose of this application is to show how you can perform simple operations using Core Data
BowTies - The main purpose of this application is to show how you can perform simple operations using Core Data

BowTies The main purpose of this application is to show how you can perform simp

CloudKit, Apple’s remote data storage service, provides a possibility to store app data using users’ iCloud accounts as a back-end storage service.
CloudKit, Apple’s remote data storage service, provides a possibility to store app data using users’ iCloud accounts as a back-end storage service.

CloudKit, Apple’s remote data storage service, provides a possibility to store app data using users’ iCloud accounts as a back-end storage service. He

OIDCLite implements the basics of getting a token using Apple's ASWebAuthenticationSession

OIDCLite While there are a few good Swift packages for Open ID Connect out there, most are /very/ heavyweight and can get quite complex. For projects

A proof-of-concept WebURL domain renderer, using a port of Chromium's IDN spoof-checking logic to protect against confusable domains

WebURLSpoofChecking A proof-of-concept WebURL.Domain renderer which uses a port of Chromium's IDN spoof-checking logic (Overview, Implementation) to p

A fast, pure swift MongoDB driver based on Swift NIO built for Server Side Swift
A fast, pure swift MongoDB driver based on Swift NIO built for Server Side Swift

A fast, pure swift MongoDB driver based on Swift NIO built for Server Side Swift. It features a great API and a battle-tested core. Supporting both MongoDB in server and embedded environments.

 SQLite.swift - A type-safe, Swift-language layer over SQLite3.
SQLite.swift - A type-safe, Swift-language layer over SQLite3.

SQLite.swift provides compile-time confidence in SQL statement syntax and intent.

Owner
Carlos Valencia
Senior Mobile App Developer -Swift, Kotlin, Flutter | @Wayfair, @Peerfit, @U-Haul
Carlos Valencia
A stand-alone Swift wrapper around the FileMaker XML Web publishing interface, enabling access to FileMaker servers.

Perfect - FileMaker Server Connector This project provides access to FileMaker Server databases using the XML Web publishing interface. This package b

PerfectlySoft Inc. 33 Jul 13, 2022
Modern interface to UserDefaults + Codable support

Default Modern interface to UserDefaults + Codable support What is Default? Default is a library that extends what UserDefaults can do by providing ex

Nicholas Maccharoli 475 Dec 20, 2022
Impelemented native Crisp chat sdk on android and ios

crisp_chat_sdk Impelemented native Crisp chat sdk on android and ios Preview Simulator.Screen.Recording.-.iPhone.13.mini.-.2022-02-07.at.18.24.56.mp4

mohammad mohammadi 2 Jul 17, 2022
Shows the issue with swift using an ObjC class which has a property from a swift package.

SwiftObjCSwiftTest Shows the issue with swift using an ObjC class which has a property from a swift package. The Swift class (created as @objc derived

Scott Little 0 Nov 8, 2021
Ios-App-ication-Swift - A simple iOS application made in Xcode using Swift

?? iPhone Calculator A simple iOS application made in Xcode using Swift. This ap

Kushal Shingote 1 Feb 2, 2022
CRRateLimitTester - Simple Clash Royale Rate Limit Tester Written Using HummingBird and Swift

CRRateLimitTester Simple Clash Royale Rate Limit Tester Written Using HummingBir

Mahdi Bahrami 0 Jan 16, 2022
CleanArchitecture - Helping project to learn Clean Architecture using iOS (Swift)

Clean Architecture Helping project to learn Clean Architecture using iOS (Swift)

Alliston 2 Dec 5, 2022
A sample application showcasing Vapor 4 connecting to an Oracle database using SwiftOracle package.

vapor-oracle A sample application showcasing Vapor 4 connecting to an Oracle database using SwiftOracle package. In this Vapor application, we create

Ilia Sazonov 3 Sep 22, 2022
iForage helps foragers to track and manage foraging spots around them using CloudKit

iForage CloudKit Preface To expand on what I've created here: https://github.com/LynchConnor/iForage, I initially developed the app using Firebase. Th

Connor Lynch 3 Jul 14, 2022
A food delivery app using firebase as the database.

FDA-ONE Food Delivery Application is a mobile application that users can use to find the best restaurant around their location and order the meals the

Naseem Oyebola 0 Nov 28, 2021