A practical interface to the Steamworks SDK using the Swift C++ importer.
Caveat Integrator: The Swift C++ importer is immature and unpredictable; this package is built on top
Current state:
SteamworksHelpers
modulemake test
builds and runs unit tests that run frame loops and access portions of the Steam API
doing various sync and async tasks.SteamworksEncryptedAppTicket
modulemake run_ticket
Below:
Steamworks
covering all of the current Steamworks APISteamworksHelpers
if worthwhile. Name etc.
changes:
SteamworksHelpers
to wrap up API patterns
involving multiple calls, usually determining buffer lengthsasync
model using @MainActor
to fix up the threads// Initialization
let steam = SteamAPI(appID: MyAppId) // or `SteamGameServerAPI`
// Frame loop
steam.runCallbacks() // or `steam.releaseCurrentThreadMemory()`
// Shutdown
// ...when `steam` goes out of scope
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.
auto handle = SteamInventory()->StartUpdateProperties();
let handle = steam.inventory.startUpdateProperties()
C++
CCallResult<MyClass, FriendsGetFollowerCount_t> 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)
Parameters carrying the length of an input array are discarded because Swift arrays carry their length with them.
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()
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<ParamName>
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.
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 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.
Prereqs:
Install the Steamworks SDK:
make install
(this is far from ideal but hard stuck behind various Swift issues)Sample Package.swift
:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MySteamApp",
platforms: [
.macOS("13.0"),
],
dependencies: [
.package(url: "https://github.com/johnfairh/steamworks-swift", from: "0.5.1"),
],
targets: [
.executableTarget(
name: "MySteamApp",
dependencies: [
.product(name: "Steamworks", package: "steamworks-swift")
],
swiftSettings: [.interoperabilityMode(.Cxx)]
)
]
)
Note that you must set .interoperabilityMode(.Cxx)
in all targets that depend on
Steamworks, and all targets that depend on them, forever and forever unto the last
dependency. This virality is part of the Swift 5.9 design and unavoidable for now.
Sample skeleton program:
import Steamworks
@main
public struct MySteamApp {
public static func main() {
guard let steam = SteamAPI(appID: .spaceWar, fakeAppIdTxtFile: true) else {
print("SteamInit failed")
return
}
print("Hello world with Steam name \(steam.friends.getPersonaName())")
}
}
API docs here.
Fully-fledged AppKit/Metal demo here.
Tech limitations, on 5.9 Xcode 15.b6:
protected
destructor? Verify by trying to use SteamNetworkingMessage_t
.SteamIPAddress
a struct and running TestApiServer
. Or change
interfaces to cache the interface pointers.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.swiftc
crashes on some uses -- on
both macOS and Linux. Check by refs to eg. CSteamNetworkingIPAddr_Allocate()
.`__ unsafe
stuff in ManualTypes.swift
.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.
HTTPRequestCompleted_t.k_iCallback
k_iSteamNetworkingUtilsCallbacks + 16
- undefined, not a clueSeems 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.
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.
Welcome: open an issue / [email protected] / @[email protected]
Distributed under the MIT license. Except the Steamworks SDK parts.
link |
Stars: 3 |
Last commit: 5 weeks ago |
Tag a 'complete' version, maybe usable!
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics