一个使用 Swift C++ 导入器的 Steamworks SDK 实用接口。
集成者注意:Swift C++ 导入器是新的且在不断发展;这个包构建于其之上
截至 Swift 6,C++ 在 macOS 上似乎终于稳定了。在 Linux 上很难推荐:仍然倾向于以奇怪的、不可移植的方式崩溃。我还没有尝试 Windows。
当前状态
SteamworksHelpers
模块中make test
构建并运行单元测试,这些测试运行帧循环并访问 Steam API 的各个部分,执行各种同步和异步任务。SteamworksConcurrency
模块中进行多线程 Steamworks 访问SteamworksEncryptedAppTicket
模块中提供加密的应用票证支持make run_ticket
下面
Steamworks
,涵盖当前所有的 Steamworks APISteamworksHelpers
。名称等更改SteamworksHelpers
,以封装涉及多个调用的 API 模式,通常确定缓冲区长度// 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
...
}
也有异步版本,例如
for await userStatsReceived in steam.userStatsReceived {
...
}
请务必查看 Swift 并发问题。
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
...
}
...
}
有异步版本
let getFollowerCount = await steam.friends.getFollowerCount(steamID: steamID)
……截至 Swift 6 终于安全了,但请检查 Swift 并发问题。
携带输入数组长度的参数将被丢弃,因为 Swift 数组本身就携带了它们的长度。
API 填充的 C++ “out” 参数在元组中返回,或者,如果 Steam API 是 void
,则作为唯一的返回值。
SteamInventoryResult_t result;
bool rc = SteamInventory()->GrantPromoItems(&result);
let (rc, result) = steamAPI.inventory.grantPromoItems()
一些 C++ “out”参数是可选的:可以将它们作为 NULL
传递,以表明调用者不需要它们。 在 Swift API 中,这些参数会生成一个额外的布尔参数 return<ParamName>
,默认值为 true
。
auto avail = SteamNetworkingUtils()->GetRelayNetworkStatusAvailability(NULL);
let (avail, _) = steamAPI.networkingUtils.getRelayNetworkStatusAvailability(returnDetails: false)
返回元组仍然会填充一些内容,但其内容是未定义的; 该库保证将 NULL
传递给底层 Steamworks API。
C++ 参数,其值很重要并且其值也会被更新,同时存在于 Swift 函数参数和返回元组中。
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)
在 API 文档建议一个值的地方提供默认值,但仍然存在 API 需要调用者为输出字符串提供最大缓冲区长度 - 这些在 Swift 中看起来非常奇怪,但无法避免。 一些 Steamworks API 支持旧的“传递 NULL 以获取所需长度”的两遍样式,并且这些模式以 Swifty 的方式封装在 SteamworksHelpers
模块中。
Steamworks 架构是基于线程的。 对于要调用 Steam API 的每个线程,必须定期调用 SteamAPI.runCallbacks()
或 SteamAPI.releaseCurrentThreadMemory()
。 前者同步回调到您的代码以完成回调; 它们都执行内部的线程特定内务处理。
Swift 并发及其内置的基于 libdispatch 的执行器完全反对用户考虑线程,只是勉强例外地考虑“主线程”。
为了将 async-await 与 Steamworks 一起使用,我认为有两种方法
将 Steam 交互保留在主线程上。 使用 @MainActor
和相关工具将您的代码保留在那里(MainActor.assumeIsolated()
可以成为救命稻草)。 如果需要从另一个隔离域调用 Steam,则必须跳过去 - 就像使用 AppKit 和 friends 一样。
作为帧循环或类似循环的一部分调用 SteamAPI.runCallbacks()
。
使用 Swift 自定义执行器来管理线程以运行您的代码并执行所需的 Steam 轮询。 将这些执行器的实例分配给 actor 以托管您的程序,巧妙地选择线程的数量和分布。
测试中的几个 (1) 示例,请参阅 TestApiSimple.testCallReturnAsync()
和 TestApiSimple.testCallbackAsync()
以及它们基于回调的版本。
SteamworksConcurrency
模块中的 (2) 的原型执行器在 SteamExecutor
中,以及 TestExecutor.testExecutorSteam()
中的使用示例。
我认为一个实际的解决方案是将它们混合起来:对一般的事情使用 @MainActor
绑定的代码,使用帧循环来触发频繁的回调,然后使用一个或多个执行器来照顾游戏服务器或较低优先级的工作。
先决条件
安装 Steamworks SDK
make install
(这远非理想,但由于各种 Swift 问题而难以解决)示例 Package.swift
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MySteamApp",
platforms: [
.macOS("15.0"),
],
dependencies: [
.package(url: "https://github.com/johnfairh/steamworks-swift", from: "1.0.0"),
],
targets: [
.executableTarget(
name: "MySteamApp",
dependencies: [
.product(name: "Steamworks", package: "steamworks-swift")
],
swiftSettings: [.interoperabilityMode(.Cxx)]
)
]
)
请注意,您必须在依赖 Steamworks 的所有目标,以及依赖于它们的所有目标中设置 .interoperabilityMode(.Cxx)
,直到最后一个依赖项。 这种传染性是当前 Swift 设计的一部分,目前无法避免。
示例骨架程序
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 文档 这里。
功能齐全的 AppKit/Metal 演示 这里。
在 Swift 6 中已基本修复。Linux 仍然有点痛苦。
技术限制,基于 6.0 Xcode 16.b3
protected
析构函数吗? 通过尝试使用 SteamNetworkingMessage_t
进行验证。SteamIPAddress
设为一个结构并运行 TestApiServer
来验证。 或者更改接口以缓存接口指针。CSteamworks
的模块接口,以查看导入器还在做什么。 可能是 Xcode 的错,仍然没有将用户的标志传递给 sourcekit,并且仍然在进行非常糟糕的错误报告。swift_shims.h
。swiftc
在某些用法上崩溃——在 macOS 和 Linux 上。 通过引用 eg. CSteamNetworkingIPAddr_Allocate()
进行检查,请参阅 steam_missing.h
。ManualTypes.swift
中的 __ unsafe
内容进行验证。从 SteamAPI_ManualDispatch_GetNextCallback()
中获取意外的 SteamAPICallCompleteds - 怀疑 steamworks 的某些部分试图在内部使用回调,而不了解手动分发模式。 或者我错过了一个 API 来分发它们。
HTTPRequestCompleted_t.k_iCallback
k_iSteamNetworkingUtilsCallbacks + 16
- 未定义,毫无头绪似乎是由使用 steamnetworking 触发的。
Facepunch 也记录并丢弃了这些,所以,嗯,我想耸耸肩。
使用 steamnetworkingmessages 时获取 src/steamnetworkingsockets/clientlib/csteamnetworkingmessages.cpp (229) : Assertion Failed: [#40725897 pipe] Unlinking connection in state 1
; 可能它不希望从 Steam ID 向自身发送消息。
捕获一些关于将 json 反射到模块中的问题的注释。
“现代” isteamnetworking
内容在某种程度上是不完整的 - Json 描述了 SteamDatagramGameCoordinatorServerLogin
, SteamDatagramHostedAddress
从头文件中丢失。 在线 API 文档在这里非常搞笑,充满了损坏的链接。 必须等待 Valve 来修复它。
我在 SDR SDK 中找到了一些,但它在 macOS 上不受支持,并且使用了实际的成熟的 C++,带有 std::string
和朋友,所以最好现在不要管它。
SteamNetworkingMessage_t
没有导入到 Swift 中。 可能会陷入带有函数指针字段的 C++ 结构体的漏洞中。 相信 Apple 最终会解决这个问题,我会编写一个零成本的内联 shim。
Json(以及所有非 C 语言)与联合体作斗争。 值得庆幸的是很少见:SteamIPAddress_t
, SteamInputAction_t
, SteamNetworkingConfigValue_t
. SteamNetworkingConfigValue_t
。 罕见到可以手动处理。
缺少大量的 out_string_count
等注释,并且有一些错误,请参阅补丁文件。
欢迎:提出问题 / johnfairh@gmail.com / @johnfairh@mastodon.social
根据 MIT 许可协议发布。Steamworks SDK 部分除外。