大多数浏览器和
Developer App 均支持流媒体播放。
-
Swift 的分布式 Actor 简介
了解分布式 Actor。它是 Swift Actor 的扩展,能够让开发分布式系统变得更加简单。我们将探索分布式 Actor 隔离和位置透明性可以如何帮助您在操作分布式 App 和系统时避免网络、序列化和其他传输问题的意外复杂化。为能更好地理解此讲座,请观看 WWDC21 的“利用 Swift Actor 保护可变状态”。
资源
- SE-0336: Distributed Actor Isolation
- SE-0344: Distributed Actor Runtime
- Swift Distributed Actors Cluster Library
- Swift Forums: Distributed Actors
- TicTacFish: Implementing a game using distributed actors
相关视频
WWDC22
WWDC21
WWDC19
-
下载
♪ 柔和乐器演奏的嘻哈音乐 ♪ ♪ 大家好 我叫 Konrad 我是 Swift 团队的工程师 欢迎来到 “Swift 的分布式 Actor 简介” 本讲座将介绍 如何 基于 Swift 并发实现跨越单线程的 App Swift actor 旨在保护您免于 同一进程中的低级数据竞争 这是通过编译时 执行 actor 隔离检查来实现的 分布式 actor 可以改善 同样的 actor 理论模型 并将其扩展到多个进程 例如集群中的多个设备或服务器 如果您还不了解 Swift actor 我们建议先去观看 来自 WWDC 2021 的 “使用 Swift actor 保护可变状态”讲座 本场讲座中我要用到的 是我们最近开发的一款井字游戏 井字鱼 它的亮点是 您可以选择 您所在的队伍 并将其配以相应表情图 可以一边玩一边标记您的步骤 然后 随着您每一步的标记 您团队的符号将被显示在画面中 直到其中一名玩家获胜 目前 我只开启了离线模式 只能和电脑选手比赛 不过下面我还会介绍几个多人模式 将我的 App 提升到一个新的高度 我已经在这个 App 中 使用 actors 来管理 并发单元以及游戏玩家了 我们看看怎样才能把这些玩家 迁移到不同的进程 以及分布式 actor 是如何帮我做到这一点的 在看代码之前 我们先思索一下 为什么 actor 这么适合 用于构建并发和分布式 App 在 WWDC 课程中 在谈到 actors 时 您可能听过这么一种表达 “并发之海” 因为这种比喻 确实很形象 假设每个 actor 都是 并发之海中的一个孤岛 它们无法直接登陆其他岛屿 而又需要彼此间交换信息 Swift 中 帮助岛屿间 传递消息的方法 是通过诸如异步方法和 async/await 来实现的 这种方法与 actor 状态隔离相结合 就能允许编译器保证在 基于 actor 的程序编译时 不受低级数据竞争的影响 现在 我们把这个理论模型 应用到我们开发的游戏中看看吧 并把它想象为一个分布式系统 我们可以把集群中的每个设备 节点 或是操作系统的进程 想象成一片独立的并发之海 这里标记为较小的深色方块 其中 我们可以轻易地同步信息 因为它们还在 共享一个内存空间 这样的消息传递理念 非常适合并发编程 以及分布式系统 不过这里也有一些限制 才能让分布式系统发挥作用 这里就是分布式 actor 发挥作用的地方 使用分布式 actor 我们就能建立 两个进程之间的通道 并在它们之间传递信息 换句话说 如果 Swift actor 是并发系统中的孤岛 分布式 actor 就是茫茫分布式系统海洋中的孤岛 经过前面一系列的操作 并没有太大变化 actor 仍然处在隔离的状态 而且仍然只能通过异步消息进行交流 我们甚至可以在同一进程中 设置更多分布式 actor 并且在各种场合和意图下 它们都和本地 actor 一样有用 唯一不同的就是他们在必要时 还能参与远程信息交互 这种能够远程访问且无需更改 分布式 actor 交互方式的能力 称为“位置透明” 这意味着无论 分布式 actor 在哪里 都可以用同样的方式与之交互 这不仅非常适合用于测试 因为本地 actor 执行相同逻辑 也使我们能把 actor 位置透明地 移动到任何地方 而无需更改它们的实现 好 我想我们已经准备好看代码了 将我们的第一个 actor 转换为分布式的了 首先 我们快速浏览一下 一般的游戏 UI 并看看这些 UI 如何与我的玩家 actor 互动 这里是一个非常标准的 SwiftUI 代码 这里有一些文本和按钮元素 来代表游戏界面 当用户点击 GameCell 时 我们会要求 玩家 actor 生成一个动作 并更新视图模型驱动 UI 有了 Swift 并发系统 这些更新全部线程安全并且表现良好 目前 代表用户输入的 actor 被实现为离线玩家 接下来我们来看看吧 这个 actor 包含了一些状态 允许它生成游戏动作 具体来说 它需要记录 自己做了多少动作 以及它为哪支队伍效力 因为每个队伍的每一步都有许多 表情图可选 移动的次数就可用于 选择表情图的角色 ID 动作生成后 我还需要更新模型 该模型是一个 MainActor 隔离类 所以改变它是线程安全的 不过调用 userMadeMove 时 我确实要用到 “await” 最后 离线玩家还声明了一个方法 每次对手移动时都会被调用 这里我们唯一需要做的 就是更新视图模型 这将再次激活游戏界面 让人类玩家可以选择动作 以此类推 一直持续到游戏结束 我们的机器玩家 也用一个 actor 来表示 它的实现实际上要比 离线玩家简单得多 因为不用更新视图模型 它只需记录 GameState 并生成游戏动作即可 由于机器玩家比较简单 我认为从它来试着转换 分布式 actor 很合适 好 我想我们已经准备好看看代码 并将我们的第一个 actor 转换为分布式 actor 了 迈向分布式井字鱼游戏的第一步 就是要将 BotPlayer 类型 转换为分布式 actor 但同时依然只在本地使用 我们打开 Xcode 看看如何做到这一点 要转换一个分布式 actor 需要导入新的分布式模块 该模块是在 Swift 5.7 中新增的 该模块包含所有必要的类型 可以用来转换和使用分布式 actor 现在可以在 BotPlayer 前面 添加 distributed 关键字 像这样 这将使 actor 自动遵守 DistributedActor 协议 并启用若干次额外的编译时检查 现在 我们看看会有什么样的错误 被编译器指出来并要求修复 编译器告诉我们 BotPlayer 没有一个可以使用的 ActorSystem 由于分布式 actor 必然从属于 某个分布式 actor 系统 该系统可以处理所有的序列化和网络工作 这些都是执行远程访问所必需的 所以我们需要 声明此 actor 打算 使用什么类型的 actor 系统 由于目前而言 我唯一的目标 就是让机器 actor 通过所有分布式隔离检查 而无需在远程主机上实际运行它 因此我可以使用 LocalTestingDistributedActor 系统 该系统自带分布式模块 告诉编译器我要使用哪个 actor 系统的方法 一种是通过选择一个模块范围内的 DefaultDistributedActorSystem 类型别名 或特定 actor 中的 ActorSystem 类型别名 后者更具体 所以我们选它吧
下一个错误是关于 “id” 属性的 是我之前手动实现的 为了让我的两个玩家 actor 都符合 Identifiable 协议 现在的错误是 ID 属性 不能显式定义 因为这样会与 分布式 actor 的合成属性相冲突 ID 是分布式 actor 的关键部分 作为一个 actor 在其 所属分布式系统中 唯一的标识 这些标识在 actor 初始化时 由分布式系统分配 并随后由该系统管理 因此 我们不能手动声明 或分配 ID 属性 而应当由系统维护 换句话说 我可以直接 将其留给系统来做 并删除我手动声明的 ID 属性 这里要处理的最后一个错误 就是分布式 actor 的初始化器 编译器说 actorSystem 属性 在使用前尚未初始化 这又是一个编译器合成的属性 属于每个分布式 actor 的一部分 我们不仅需要声明想要使用的 actor 系统的类型 而且还需要初始化 合成的 actorSystem 属性 通过具体的 actor 系统 一般来说 这里可以做的 就是在初始化器中 接受一个 actor 系统 并将其传递给属性 这样 就可以在测试中传入 一个不同的 actor 系统 以帮助进行简单的单元测试 我们还要在创建每个新机器玩家时 传入一个实例 所以我们现在试试
很好 所有声明方面的错误都修复了 但还有一些调用的错误 需要解决 看来只有分布式方法 可以在远程分布式 actor 上调用 这类似于仅将系统中的某些 actor 标记为 distributed actor 分布式 actor 上不是每个方法 都是为远程调用而设计的 这些方法可以是小的辅助函数 还有些函数 假定调用者已有授权 这就是为什么 Swift 要求明确说明 您想给远程调用者 展示什么样的分布式 API 界面 好在要修复这点也容易 只需 给这些函数添加 distributed 关键字即可 由于 makeMove 和 opponentMoved 的方法 都是要被远程调用的 我给它们两个都加上 distributed 关键字
好 这样就只剩最后一件事 要解决了 由于分布式方法调用可以跨网络 我们需要确保它们的所有参数 及返回值都符合 actor 系统的 序列化要求 这个例子中 actor 系统使用 Codable Swift 的原生序列化机制 具体来说 编译器会告诉我们 “结果类型 GameMove 不符合 Codable 的序列化要求” 我来快速浏览一下 GameMove 类型 幸运的是 这似乎是一个干净的小数据类型 很容易通过添加必要的符合性 将其改为 Codable Swift 编译器会 自动合成必要的 Codable 实现 这样 就全部完成了 我可以检查一下 看游戏是否能正常运行
好 鱼队得分 尽管机器玩家目前仍是在 同一个本地设备上运行 我们已经为 激动人心的下一步铺好了路 下一步 我们将真正从 机器玩家新获得的 位置透明能力中获益 我已经准备了一个 基于 WebSocket 的 示例 actor 系统用于演示 使用这个 actor 系统 就能够将我们的机器玩家 转移到服务器端 Swift 应用中 并从我们的手机游戏中 对它进行远程引用 就 actor 而言 我们只需从 LocalTestingDistributedActor 系统 将所声明的 actor 系统 改为 SampleWebSocketActorSystem 即我为演示所准备的示例 App 其余的 actor 代码保持不变 接下来 我们来解析一个 远程机器玩家引用 而不是在本地创建一个 需要记住的是“本地”和“远程” 在涉及到分布式 actor 时 只是从视角而言的 每一个远程引用 在分布式 actor 系统的 某个其他节点上都有 一些对应的本地实例 创建分布式 actor 的本地实例 执行方式与任何 其他 Swift 对象大致相同 都是通过调用它的初始化程序 然而 获取 对分布式 actor 的远程引用 所遵循的模式略微有所不同 不是创建一个 actor 而是使用具体的 actor 系统来 尝试解析一个 actor ID 这种静态解析方法让我们能够 要求 actor 系统 给我们该 actor ID 一个已有的 actor 实例 或返回一个 该 actor 的远程引用 Actor 系统不应在 解析标识符时 执行实际的远程查找 原因如下 该解析方法不是异步的 因此会很快返回结果 并且不执行任何联网 也不执行阻塞操作 如果一个身份验证看起来有效 并且似乎指向 一个有效的远程位置 系统就会假定 这样的 actor 存在 并返回对它的远程引用 要记住 在解析 ID 时 远程系统上的实际实例 可能根本不存在 例如 这里正在生成一个 对手机器玩家的 随机标识符 可以专门用来和我们玩游戏 该玩家尚不存在 不过一旦服务端系统上 收到第一条指定给该 ID 的信息 该玩家就会创建成功 现在我们接着看 服务器端的 Swift 应用 有了准备好的 WebSocket actor 系统示例 这个操作将会轻而易举 首先 在服务器模式下创建 WebSocket actor 系统 这会将其绑定并监听端口 而不是连接端口 我们让 App 等到系统终止 然后我们需要以某种方式处理 根据所收到的 尚未分配实例的 actor ID 信息 创建相应的 actor 模式 一般情况下 actor 系统会接收传入的信息 尝试解析他们的收件人 ID 以定位一个 本地分布式 actor 实例 然后对定位的 actor 执行远程调用 正如我们刚刚讨论的那样 我们的机器玩家 ID是生造的 所以系统不可能知道它的存在 更不用说自己创建 正确类型的 actor 了 好在 我们的示例 actor 系统 已经为我们准备了正确的模式 按需创建 actor 请注意 这只是一个模式 并不是内置的 或者由分布式 actor 模块提供的 不过这正是一个很好的例子 说明 actor 系统的灵活与强大 一个系统可以提供多种模式 并使复杂的任务易于处理 使用这种模式 actor 系统可以 像往常一样为所有传入的 ID 解析一个本地 actor 但是 当它找不到现有的 actor 时 它会尝试 resolveCreateOnDemand 由于组成 ID 的客户端代码 和服务器代码都由我们控制 因此我们可以通过 按需创建必要的 actor 来帮 actor 系统解决问题 由于我们在客户端上生成的 机器标识符使用的是 可识别的命名方案 比如给 ActorIdentity 添加标签 或者直接使用一些可识别的名字 我们可以检测这些 ID 并为每条尚未指定 actor 的消息 创建一个新的机器对手 只有收到第一条指定信息时 我们才会创建一个新的机器玩家 因为后续的远程调用 可以直接解析现有实例 就是这些了 现在 我们已经完成了服务器设置 可以用远程机器玩家来玩游戏了 可以使用 Swift run 从命令行运行服务器 也可以像往常一样使用 Xcode 选择服务器方案并单击 我们走完第一步后 就会通过调用我们所创建的 远程玩家引用上的 makeMove 来要求机器玩家跟着走一步 这会触发服务器端系统中的解析 解析无法为此 ID 找到一个现有实例 因此会尝试按需创建一个 并成功了 机器会收到一个 makeMove 调用 并使用它生成的 GameMove 进行回复 这样已经很棒了 虽然我们还需要做一些前期工作 将我们的机器玩家转换为分布式 actor 不过将其移动到远程系统 其实很简单 而且我们不必处理任何联网 或序列化实现的细节 这些麻烦的工作 分布式 actor 系统 都已经帮我们做好了 虽然目前还没有多少 功能完整的操作可用 这种方便的分布式操作 正是此功能所要实现的目的 接下来 我们来看看如何 为我们的游戏创建 真正的多人游戏体验 之前的例子使用了 客户端/服务器端场景的 分布式 actor 这点您从其他 App 的 开发过程中可能已经熟悉了 不过 分布式 actor 还可以用于 P2P 系统中 这样的系统中根本没有 专用的服务器组件 这符合我对我们游戏的另一个设计 有时出门在外 总能碰上一些地方 没有很好的互联网 但本地 Wi-Fi 效果不错 这种时候我还想 跟连接到同一个网络里的朋友 玩上几局游戏 怎么办呢? 我可以直接实现 另一个 actor 系统 这次使用网络框架提供的 本地网络功能 本节中我们不会深入探讨 这样的 actor 系统 可以去看 WWDC 2019 上的 “网络新功能 第二部分” 来了解如何实现此类自定义协议 值得一提的还有一点 访问本地网络 可能会暴露隐私信息 因此要注意 谨慎使用 由于这次我们要处理的是 其他设备上已经存在的 分布式 actor 因此不能像前面的例子那样 直接创建 ID 而是要找到另一台联机游戏设备上的 具体的 actor 这个问题不是 分布式 actor 独有的 且通常可以用服务发现机制来解决 不过 在分布式 actor 领域 API 有一个共同的模式和风格 actor 系统应当提供这种模式 以支持在所有代码中 坚持使用强类型 API 我们称之为接待员模式 因为这种模式类似于酒店 actor 要登记入住 才能让他人知道您 来找您见面 每个 actor 系统 都有自己的接待员 并且可以使用任何方式 实现发现 actor 的底层传输机制 有时这可能需要现有的服务发现 API 并只在其上添加一个类型安全的 API 或采取基于 Gossip 的模式 或是完全不同的其他机制 不过 这是一个实施上的细节问题 涉及到的是 actor 系统用户的角度 我们需要关心的是 让我们的 actor 登记 好让它能被发现 并在需要发现它们的时候 能够通过标签或类型来查找 现在看看我为 SampleLocalNetworkActorSystem 设计的一个简单的接待员 一个 actor 可以登记 并使所有接待员都可以 在分布式 actor 系统中发现它 然后我们可以获得一个列表 里面会罗列某类型的所有 actor 并在该系统中可用时将其进行标记 现在我们用这个接待员来发现 我们想一起玩游戏的 某个具体的对手 actor 吧 之前 我们的 GameView 直接创建了 或者说解析了 视图初始化程序中的一个对手 现在不行了 因为我们需要异步 等待对手出现在网络上 为此 我添加一个匹配视图 来在我们寻找对手的时候 显示“正在寻找对手”的消息 当这个视图出现时 匹配就会开始 对接会在一个 新的非结构化任务中完成 我们会在任务中 要求本地 actor 系统的接待员 使用对方队伍的标签 列出所有标记的 actor 那么 如果我们为鱼队而战 我们就要找啮齿动物队的队员 反之同理 接下来 我们将使用异步 for 循环 来等待即将出现的对手 actor 当系统发现附近有能跟我们玩的 对手的设备时 这个任务循环将被恢复 现在我们假设对手随时准备玩游戏 并立即将其存储在我们的模型中 并与他们开始游戏 我们使用辅助函数来决定 谁走第一步 最后 通知对手我们想要开始玩了 记得要返回这里 才能跳出 异步 for 循环 因为我们只需要一个对手 来完成我们的配对任务 对于这种游戏模式 我们有必要 修改一下我们的 OfflinePlayer 实现 姑且将之称为 LocalNetworkPlayer 吧 这个玩家将使用 SampleLocalNetworkActorSystem 最有趣的是 代表人类玩家的 actor 使用这种 makeMove 方法 就可以远程调用了 但实际采取行动的还是 人类玩家 为了解决这一挑战 我们可以在我们的视图模型中 引入一个 humanSelectedField 异步函数 它由一个 @Published 值驱动 该值在人类用户单击 其中一个字段时激活 当人类玩家点击一个字段时 我们的 makeMove 函数就会恢复 我们就通过将已执行的 GameMove 返回给远程调用者来完成远程调用 又一次 全部完成了 虽然我们要 改变一点 actor 的设计 才能实现真正的多人游戏模式 但我们并没有改变 系统的整体设计 而最重要的是 我们的游戏逻辑更改中 跟是否使用本地网络 没有具体关联 我们发现对手并与他们玩游戏 是通过在玩家 actor 上 调用分布式方法 要演示这个游戏模式 我需要一个对手 我们来问问我毛茸茸的助手 Caplin the Capybara 吧 听说他很厉害哦
好吧 他很聪明
他很擅长玩这个 我试试这里 哦 他赢了我 这次您赢了 小家伙 不过还有下次的 谢谢您的帮助 Caplin 最后 我再来讲讲 不同的 actor 系统组合 可以实现什么效果吧 比如我们可以使用 WebSocket 系统 在服务器端大厅系统中 注册设备端 actor 玩家 将他们配对并充当代理 完成他们彼此之间的分布式调用 我们可以实现一个 GameLobby actor 用来让设备端的玩家 actor 自行注册 设备进入在线游戏模式时 他们会通过接待员 发现 GameLobby 进行调用并加入 GameLobby 会记录现有玩家 并在确定一对玩家后 开启一个游戏会话 游戏会话将充当游戏的驱动 在服务器存储的游戏表征中 轮询动作并标记动作 随着游戏结束 我们可以收集结果 并将报告返回给大厅 不过更有趣的是 我们可以水平扩展这个设计 当然还可以创建更多的游戏会话 actor 在单个服务器上同时提供更多游戏 不过有了分布式 actor 我们甚至还可以在其他节点上 创建一个游戏会话 来在集群内平衡 同步运行的游戏数量 也就是说 如果我们有一个 集群 actor 系统就好了 而事实上 还真的有 我们为这种情况开源了一个 功能丰富的 Cluster Actor 系统库 供您使用 该库使用 SwiftNIO 来实现 专用于服务器端数据中心集群 它应用了先进的故障检测技术 并附带自己的 集群范围内的接待员 我们推荐您去了解一下 因为它既是一种先进的 actor 系统引用实现 而且有着强大的服务器端应用 现在我们回顾一下 本次课程中学到的知识 首先 我们了解了分布式 actor 以及如何提供额外的有编译器辅助的 actor 隔离和序列化检查 我们了解了如何用它实现位置透明 以及如何利用它来解放 actor 使其无需与其调用者 位于同一进程 我们还看了一些 actor 系统的实现示例 为您提供一些启发 知道用分布式 actor 可以构建哪些东西 分布式 actor 好不好用 取决于其所在的 actor 系统 所以这里为了供大家参考 列出了本课中看到的各种 actor 系统 本地测试系统 默认随 Swift 一起提供 还有两个示例 actor 系统: 一个是客户端/服务器风格的 基于 WebSocket 的系统 还有一个是基于本地网络的 这些系统相当不完整 更多地是作为一种灵感 让您对能使用分布式 actor 构建什么样的内容有所启发 可以在本课附带的 示例代码 App 中查看 还有最后一个 开源的 功能齐全的 服务器端集群实现 现在作为 Beta 软件包提供 将与 Swift 5.7 一起完善 要了解有关分布式 actor 的更多信息 可以参考以下资源 本课附带的示例代码 其中包括我们井字鱼游戏的所有步骤 这样您就可以自己深入研究代码了 还有 Swift 进化提案 结合分布式 actor 语言功能 详细解释了它们的驱动机制 您也可以访问 Swift 论坛 里面能找到为 actor 系统开发者和用户 准备的分布式 actor 类别 感谢收看 期待看到 您利用分布式 actor 设计的 App ♪
-
-
4:49 - actor OfflinePlayer
public actor OfflinePlayer: Identifiable { nonisolated public let id: ActorIdentity = .random let team: CharacterTeam let model: GameViewModel var movesMade: Int = 0 public init(team: CharacterTeam, model: GameViewModel) { self.team = team self.model = model } public func makeMove(at position: Int) async throws -> GameMove { let move = GameMove( playerID: id, position: position, team: team, teamCharacterID: team.characterID(for: movesMade)) await model.userMadeMove(move: move) movesMade += 1 return move } public func opponentMoved(_ move: GameMove) async throws { do { try await model.markOpponentMove(move) } catch { log("player", "Opponent made illegal move! \(move)") } } }
-
5:39 - actor BotPlayer
public actor BotPlayer: Identifiable { nonisolated public let id: ActorIdentity = .random var ai: RandomPlayerBotAI var gameState: GameState public init(team: CharacterTeam) { self.gameState = .init() self.ai = RandomPlayerBotAI(playerID: self.id, team: team) } public func makeMove() throws -> GameMove { return try ai.decideNextMove(given: &gameState) } public func opponentMoved(_ move: GameMove) async throws { try gameState.mark(move) } }
-
6:11 - distributed actor BotPlayer
import Distributed public distributed actor BotPlayer: Identifiable { typealias ActorSystem = LocalTestingDistributedActorSystem var ai: RandomPlayerBotAI var gameState: GameState public init(team: CharacterTeam, actorSystem: ActorSystem) { self.actorSystem = actorSystem // first, initialize the implicitly synthesized actor system property self.gameState = .init() self.ai = RandomPlayerBotAI(playerID: self.id, team: team) // use the synthesized `id` property } public distributed func makeMove() throws -> GameMove { return try ai.decideNextMove(given: &gameState) } public distributed func opponentMoved(_ move: GameMove) async throws { try gameState.mark(move) } }
-
12:08 - Resolving a remote BotPlayer
let sampleSystem: SampleWebSocketActorSystem let opponentID: BotPlayer.ID = .randomID(opponentFor: self.id) let bot = try BotPlayer.resolve(id: opponentID, using: sampleSystem) // resolve potentially remote bot player
-
13:35 - Server-side actor system app
import Distributed import TicTacFishShared /// Stand alone server-side swift application, running our SampleWebSocketActorSystem in server mode. @main struct Boot { static func main() { let system = try! SampleWebSocketActorSystem(mode: .serverOnly(host: "localhost", port: 8888)) system.registerOnDemandResolveHandler { id in // We create new BotPlayers "ad-hoc" as they are requested for. // Subsequent resolves are able to resolve the same instance. if system.isBotID(id) { return system.makeActorWithID(id) { OnlineBotPlayer(team: .rodents, actorSystem: system) } } return nil // unable to create-on-demand for given id } print("========================================================") print("=== TicTacFish Server Running on: ws://\(system.host):\(system.port) ==") print("========================================================") try await server.terminated // waits effectively forever (until we shut down the system) } }
-
20:02 - Receptionist listing
/// As we are playing for our `model.team` team, we try to find a player of the opposing team let opponentTeam = model.team == .fish ? CharacterTeam.rodents : CharacterTeam.fish /// The local network actor system provides a receptionist implementation that provides us an async sequence /// of discovered actors (past and new) let listing = await localNetworkSystem.receptionist.listing(of: OpponentPlayer.self, tag: opponentTeam.tag) for try await opponent in listing where opponent.id != self.player.id { log("matchmaking", "Found opponent: \(opponent)") model.foundOpponent(opponent, myself: self.player, informOpponent: true) // inside foundOpponent: // if informOpponent { // Task { // try await opponent.startGameWith(opponent: myself, startTurn: false) // } // } return // make sure to return here, we only need to discover a single opponent }
-
20:23 - distributed actor LocalNetworkPlayer
public distributed actor LocalNetworkPlayer: GamePlayer { public typealias ActorSystem = SampleLocalNetworkActorSystem let team: CharacterTeam let model: GameViewModel var movesMade: Int = 0 public init(team: CharacterTeam, model: GameViewModel, actorSystem: ActorSystem) { self.team = team self.model = model self.actorSystem = actorSystem } public distributed func makeMove() async -> GameMove { let field = await model.humanSelectedField() movesMade += 1 let move = GameMove( playerID: self.id, position: field, team: team, teamCharacterID: movesMade % 2) return move } public distributed func makeMove(at position: Int) async -> GameMove { let move = GameMove( playerID: id, position: position, team: team, teamCharacterID: movesMade % 2) log("player", "Player makes move: \(move)") _ = await model.userMadeMove(move: move) movesMade += 1 return move } public distributed func opponentMoved(_ move: GameMove) async throws { do { try await model.markOpponentMove(move) } catch { log("player", "Opponent made illegal move! \(move)") } } public distributed func startGameWith(opponent: OpponentPlayer, startTurn: Bool) async { log("local-network-player", "Start game with \(opponent.id), startTurn:\(startTurn)") await model.foundOpponent(opponent, myself: self, informOpponent: false) await model.waitForOpponentMove(shouldWaitForOpponentMove(myselfID: self.id, opponentID: opponent.id)) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。