ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swiftの分散アクタの紹介
分散アクタとは - Swiftアクタモデルから拡張されたアクタで、分散システムの開発を合理化します。分散Appまたはシステムで作業する際に、分散アクタの分離や位置透過性により、ネットワーク化、シリアル化、他の転送関係での予期せぬ複雑化が回避される仕組みについて解説します。 このセッションを最大限に活用するには、WWDC21の「Swiftアクターによるミュータブルステートの保護」をご覧ください。
リソース
- 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 teamのエンジニアです 『Swiftの分散アクタの紹介』へようこそ このセッションでは単一のプロセスを超えた Swift適合ベースAppについて ご紹介します Swift actorは同じプロセスでの 低レベルのデータ競合を防ぐためのもので コンパイル時間実行のactor隔離確認で行います 分散 actorは 同じ概念のactorモデルを改良し クラスタ内の複数のデバイスやサーバなど 複数のプロセスに拡張します Swift actorをよくご存じでない方は まず WWDC 2021の 『Swiftアクターによる ミュータブルステートの保護』を ご覧ください このセッションで作業するAppは 私が最近開発している「Tic Tac Fish」という 三目並べに似たゲームです これの面白い部分はプレイするチームを 選択するとそれが絵文字に対応し ゲームのコマになるところです 盤でコマを動かすと どちらかプレイヤが勝つまで 絵文字が盤に配置されます 現在はオフラインモードで実装しており ボットと対戦できますが 複数プレイヤモードを導入して Appをレベルアップしたいと考えています 適合性とゲームのモデルプレイヤを 管理するためにすでにactorを使用しています ではプレイヤactorを 分散 actorを活用して他のプロセスに 取り入れる方法を見てみましょう コードに入る前に少し話を戻して actorが適合性と分散型Appに 適しているかを説明します WWDCセッションを通してactorについて話す場合 「適合性の海」という言葉を聞くでしょう それはactorについて考えた時 いいメンタルモデルになるからです 各actorは適合性の海に浮かぶ島で それぞれの島に直接アクセスする代わりに メッセージを交換し合います Swiftではこのメッセージの送信が async/awaitというメソッドで実行されます これと actor状態隔離を組み合わせることで コンパイラはactorベースのプログラムが コンパイルされた時に 低レベルのデータ競合から解放されます 同じ概念のモデルを 分散システムとして再考し このゲームに適用してみます クラスタ内の各デバイス ノード オペレーティングシステムのプロセスを 個別の適合性の海だと考えます ここでは暗い四角で囲まれています この中でどちらかというと簡単に 情報を同期できますなぜなら同じ メモリスペースを共有しているからです 同じ概念のメッセージのパスは 適合性と分散に対し うまく機能しますが すべてを機能させるには 分散にいくつかの制限を加える必要があります ここで分散 actorの登場です 分散 actorを使用すると2つのプロセス間の チャネルを設定しメッセージを送信でき つまりSwift actorが適合性の海の 島であるなら分散 actorは 分散システムの海で同じ役割を果たします プログラミングモデルでは特に変化はなく actorは個々の状態に隔離され 非同期メッセージで通信します 同じプロセスで分散 actorを 増やすこともできます あらゆる点で ローカルactorのように便利で 違いは 必要に応じてリモート通信に 参加する準備ができていることです この 分散 actorとのやり取りの方法を 変更せずにリモートできる機能を 「位置透明性」と呼びます これは分散 actorの ロケーションに関係なく 同じ方法で通信できるという意味です ローカルactorで同じことを実行するための テストに最適であるだけでなく 実装を変更することなく actorを位置すべき場所に 移動させることができます ではコードに移り 最初のactorを分散 actorに返還しましょう まず一般的なゲームUIを見て 私のプレイヤactorとどう交信するか見てみます このビューは標準的なSwiftUI コードです ゲームフィールドを表現する テキストとボタンの要素がいくつかあります ユーザがGameCellをクリックすると プレイヤ actorに移動し ビューモデルを更新するようリクエストします Swift Concurrencyのおかげで すべての更新は行儀がよくスレッドセーフです 現在ユーザ入力を表現するactorは オフラインプレイヤとして実装されています 次にそれを見てみましょう このactorは ゲームの動きを生成することを 可能にする状態を要約しています 具体的には すでに行われた動きを追跡し どのチームでプレイされているかを追跡します 各チームには各動きに対して選択できる 絵文字があり動きの数を使用して 絵文字キャラクタIDを選択するからです 動きが作成されたらモデルを 更新する必要があります モデルはMainActor隔離クラスなので この変形はスレッドセーフです userMadeMoveの呼び出しでは 「await」を使用する必要があります 最後にオフラインプレイヤは対戦相手が 動いた時に呼ばれるメソッドを宣言します ここではビューモデルを変更するだけです ゲームフィールドがアクティブになり 人間のプレイヤは動きを選択し ゲーム終了までこのサイクルが続きます ボットプレイヤもactorで表現されています この実装はビューモデルの更新について 心配する必要がなく オフラインプレイヤよりもシンプルです GameStateを追跡し ゲームの動きを生成してくれます ボットプレイヤはよりシンプルなので まずこれを分散 actorに 変換しましょう コードを使って 最初のactorを分散 actorに変換します 分散型の「Tic Tac Fish 」に する最初のステップは BotPlayer 型を ローカルで使用しながら 分散 actorに変換することです Xcodeでそれを行います 分散 actorを宣言するには Swift 5.7で紹介された 新しい分散型モジュールを インポートする必要があります このモジュールには分散 actorを宣言し 使用するために必要な すべての型が揃っています 分散キーワードをこのように ボットプレイヤactor宣言の前に配置します これによりactorは自動的に DistributedActorプロトコルに適合し 追加のコンパイル時間確認を有効にできます どんなエラーを修正するよう コンパイラに要求されるか見てみましょう コンパイラはBotPlayerが一緒に使用できる actorystemを宣言していないと言っています 分散 actorは 何らかの 分散 actorシステムに属し 遠隔呼び出しの実行に必要なシリアル化や ネットワーキングを処理します このactorが使用しようと意図する actorシステムを宣言する必要があります 現在の私の目標はボットプレイヤがすべての 遠隔ホストで実行されずに 分散隔離検査に合格することなので 分散型モジュールに付いてくる LocalTestingDistributedActor システムを使用できます コンパイラに 使用するactorシステムを モジュール全体の DefaultDistributedactorystem タイプエリアスで宣言するか 特定のactorのactorystem タイプエリアスを使用します 後者の方がより具体的なため これを使用しましょう
次のエラーは「id」プロパティで 両方のプレイヤactorが適合する必要のある 特定可能プロトコルに適合するために 私が手動で実装したものです このエラーではID プロパティが 分散 actorの合成プロパティとの競合で 明確に定義できないというメッセージです IDは分散 actorにとって重要な部分です 関連の分散 actorシステム全体で actorを一意で特定するのに使用されます actorが初期化されると 分散 actorシステムにより割当てられ その後そのシステムにより管理されます その場合IDプロパティを手動で 宣言したり割当てたりできません actorシステムが代わりに行います つまりそれはactorシステムに任せて 手動で宣言したIDプロパティを削除します 最後に修正する必要のあるエラーは 分散 actorの初期化子です コンパイラはactorystemプロパティが 使用前に初期化されていないと言っています こちらもすべての分散 actorの一部である コンパイラ合成プロパティです 使用したいactorシステムの型を 宣言するだけでなく合成 actorystem プロパティを具象 actor システムで 初期化する必要があります ここで適切なアクションは 初期化子でactorシステムを承認し プロパティにパスすることです そうすればテストにある異なるactorシステムの 実装にパスして簡単な 単体テストを促進できます 新しいボットプレイヤを作成するたびに インスタンスをパスする 必要があり 今から行います
これで宣言のエラーはすべて解決しました でもまた呼び出しのエラーに 対処する必要があります 分散型メソッドのみが遠隔の分散 actorを 呼び出せるようです これはシステム内の一部の actor だけを 分散 actorとして注釈するのに似ています 分散 actorではすべてのメソッドが 遠隔呼び出し向けに設計されたとは限りません ヘルパー機能や呼び出し元がすでに 承認されたと推定する 関数を使うこともできます そのため Swiftは分散型 API 表面について 明確に表現するよう求めています 遠隔呼び出し元に露出するためです ありがたいことにこの問題は これらの関数に 分散型キーワードを足せば修正できます makeMove とopponentMoved メソッドは 遠隔で呼び出されるはずなので 両方に分散型キーワードを足します
これで最後の問題が 片付きました 分散型メソッド呼び出しは ネットワーク境界を越えるため すべてのパラメータとリターン値が actorシステムでシリアル化要件に 適合するのを確実にする必要があります ここではactorシステムは Codableを使用しています Swift のネイティブシリアル化メカニズムです コンパイラは具体的に 「結果型 GameMove がシリアルか要件Codableに 適合していない」と言っています なので GameMove型を見てみましょう はっきりしたデータ型のようで 必要な適合性を追加すれば 簡単にコード化可能にできるようです Swiftコンパイラは必要なコード化可能の 実装を合成してくれます これで完了 期待通りにゲームが実行されるでしょう
魚チームに1点 どちらのプレイヤも同じローカルデバイスで 実行されていますが次のステップのために すでに下準備ができています このステップではボットプレイヤが新しく得た 位置透過性能力の恩恵を受けます これに使用するためのWebSocketベースの サンプルのactorシステムを すでに準備してあります このactor システムを使用することで ボットプレイヤをサーバーサイドの Swift Appに移動させ このモバイルゲームから リモート参照をresolveします actorに関しては 宣言されたactorシステムを LocalTesting DistributedActorシステムから 私がサンプルAppで用意した SampleWebSocketactorystemに変更するだけです 残りの actor コードは同じです 次は遠隔ボットプレイヤ参照を ローカルで作成せずに問題を解決します 分散 actorにおいて「ローカル」と 「遠隔」という用語は観点によるものです すべての遠隔参照には分散 actor システムの その他のノードでローカルインスタンスと 一致しています 分散 actorのローカルインスタンスの作成は 他のSwift オブジェクトと初期化子を呼び出し 同じ方法で行われます しかし分散 actor に遠隔参照を得ることは パターンが少し異なります actorを作成する代わりに 具象 actor システムで actor IDの問題を解決します 静的resolveメソッドによりactor システムに そのIDを持つ actorのactorインスタンスを 渡すようリクエストするか IDにより特定されたactorに 遠隔参照を与えるよう依頼します actor システムは識別子をresolveする際に 遠隔検索を行うべきではありません なぜなら resolveメソッドは非同期ではないため すばやくリターンし ネットワーキングの実行を防がないと 操作を中断させてしまいます 識別は有効な遠隔ロケーションでは 有効に見えても システムは そのactorが存在すると みなし遠隔参照をリターンします IDをresolveする際に忘れてはいけないのは 遠隔システムの実際のインスタンスは まだ存在していない可能性があることです たとえばここではゲームの対戦相手となる 敵のボットプレイヤを 適当に作りました このボットはまだ存在しませんが このIDが受信するよう割当てられた 最初のメッセージとして サーバ側に作成されています サーバ側のSwift Appですが 私が準備したサンプルの WebSocket actorシステムで 簡単に実装することができます まずサーバモードでWebSocket actorシステムを作成します これはポートを接続ではなく結び付け 内容を聞きます そしてシステムが終了するまでAppを待たせます 次はまだインスタンスが割当てられていない IDに向けられたメッセージを 受信した時のためにオンデマンドで作成する actorのパターンに対処する必要があります 一般的にactorシステムはメッセージを受信し ローカル分散 actor インスタンスを見つけるため 受信者IDを解決しようとし 位置が判明したactorで遠隔呼び出しを行います しかし先程お話ししたように ボットプレイヤのIDは適当につけたものなので システムはそれらを知ることができず 自身で適切なactorの型を書きます しかし サンプルのactor システム実装のおかげで オンデマンドのactor作成用に適切な パターンが十分に作成されています ここで注意して頂きたいのは これはただのパターンで 分散 actorモジュールが構築したものや 提供するものではありません しかしactorシステムの実装が いかに柔軟で強力かを示す例となります システムはさまざまなパターンを提供し 複雑なタスクの処理を容易にしてくれます このパターンの使用によりactorシステムは 通常通りすべての受信IDの ローカルactorを解決します しかし既存のactorを見つけられない場合 resolveCreateOnDemandを試みます 私たちは両方のクライアント コードを管理するため IDとサーバコードの一部を作り上げ 必要なactorをオンデマンドで作成して actorシステムを支援することができます クライアントで適当に作った ボットの識別子が「ActorIdentityに タグを追加」など 一般的な 命名スキームを使用するか はっきりした名前を使用するため これらのIDを検知しまだactiveが1つもない すべてのメッセージに対し ボット対戦者を作成できます 新しいボットは指定された 最初のメッセージにのみ作成します 後続の遠隔呼び出しは ただ既存のインスタンスをresolveするだけです 以上です サーバ実装は完了したので 遠隔ボットプレイヤとゲームができます コマンドラインからシステムを実行するか Xcodeでサーバスキームを選択し 通常通り「実行」をクリックします 最初の動きをしたところで 作成した遠隔プレイヤ参照で makeMoveを呼びボットに同じ事をさせます サーバ側のシステムでresolveがトリガされます このIDの既存のボットが見つからないので オンデマンドでボットを作成します ボットはmakeMove呼び出しを受け 生成されたGameMoveで応答します すでにすばらしいゲームになりました ボットプレイヤを分散actorに変換する 前面の作業を行いましたが 遠隔システムに移動するのは わりと簡単でした ネットワーキングやシリアル化実装の 詳細に携わる必要はありませんでした すべての重量な仕事は 分散 actorシステムが行いました そしてまだ強化された フィーチャーコンプリートの 実装はありませんが この機能ではこのような簡単な 配信を目指してきました 次はこのゲームに 真のマルチプレイヤ体験を構築しましょう 先程の例ではクライアント/サーバ シナリオで分散actorを使用しました きっと他のAppを開発したことでご存じでしょう しかし 分散 actorは 専用のサーバコンポーネントがない ピアツーピアシステムでも使用できます これをこのゲームの別のアイデアに使用します 時々旅先でインターネットの接続が あまりよくなくでもローカルWi-Fiが すばらしく機能することがあります でも友達に挑戦して ゲームで遊びたいと思います 同じネットワークに接続した場合に 今のような状況が起こったとしましょう 別のactorシステム実装を 実装しました 今度はネットワークフレームワークが提供する ローカルネットワーキング機能を使用します actorシステムの実装については このトークでは説明しませんが WWDC 2019の 『ネットワーキングの最新事情(パート2)』 でこれを学ぶことができます また ローカルネットワークへのアクセスは 機密情報をさらすことにもなりかねません なので慎重に扱ってください 今回は既存の他のデバイスの 分散actorを使用するので 先程の例ようにローカルIDを 作り上げることはできません ゲームで遊びたい別のデバイスの 特定のactorを見つけます この問題は分散actorでは特別ではなく サービスディスカバリ機構で解決できますが 分散actorのドメインでは actorシステムが期待する一般的なパターンと APIの様式がありそれによりすべてのコードで 強く型付けされたAPIを利用することができます これは受付パターンと呼ばれています actorは他のものと出会うために ホテルのようにチェックインするからです 各actorシステムには専用の受付がおり 基本的なトランスポート機構に最適な方法で actor発見可能性を実行します 既存のサービス発見性APIを使用することもあり 型安全APIの層だけが上にあります またはゴシップベースの機構を使用するか 完全に別のものを使用します しかしこれはユーザのactorシステムの 観点からの実装の詳細なので 大切なのはactorが 発見可能にすることで 発見が必要な時にタグか型で 検索できるようにすることだけです SampleLocal Networkactorystem向けに 実装したシンプルな受付をお見せしましょう actorがチェックインすると 分散actorシステムの すべての受付がそのactorを発見します そうすると特定の型のactorのリストを取得し そのシステムで利用可能に なるとタグを付けます ではこの受付を使って ゲームの対戦相手にしたい 具象の対戦相手actorを見つけましょう 先程GameViewは対戦相手をビュー初期化子で 直接作成…resolveしました ここではそれができません 相手がネットワークに 現れるまで 非同期で待つ必要があるからです これを行うには見つかるのを待つ間に 「Looking for opponent」というメッセージを 表示するマッチメイキングビューを使用します このビューが表示されたら マッチングメイキングが始まり これは 対戦チームのタグを使用し ローカルactorシステムの 受付にタグ付けされた すべてのactorのリストを要求する 非構造化タスクで行われます 魚チームでプレイする場合 相手にネズミチームを探したり その反対が行われたりします 次に敵のactorのawaitに async for loopを使用します システム対戦相手のいる デバイスを発見すると タスクループが再開します 相手はプレイする準備が万端なら 即座にモデルに保管され ゲームを開始できます ヘルパー機能を使用して誰が 先手を打つか決め対戦相手に 対決したいことを伝えます async for loopを解除するために ここに戻りますマッチングは1人しか 必要ないからです このgameplayモードでは OfflinePlayerの実装を少し変更します LocalNetworkPlayerと名付けましょう SampleLocalNetworkactorystemが使用されます ここで興味深いのは人間のプレイヤを代表する actorが遠隔で 呼び出される点です しかし手を打つのは 人間のプレイヤの責任です この課題を解決するために ビューモデルに非同期の関数 humanSelectedFieldを導入します 人間のユーザーが1つの フィールドでクリックすると トリガーし@Published値により起動します 人間のプレイヤがフィールドをクリックすると makeMove関数が再開し実行されたGameMoveを 遠隔呼び出し元に返すことで完了します これも以上で完了です actor実装を少し変更して 真の複数プレイヤゲームモードにしましたが システムの全体的な設計は 特に変更されていません 最も重要なのは ゲームロジックが変更されていない点が ローカルネットワークの 使用を具体的にしました プレイヤactorで分散メソッドを呼び出し 対戦相手とゲームができるようになりました これを実演するには対戦相手が必要です アシスタントのカパビラにお願いしましょう かなり凄腕だそうです
彼は頭がいいですね
なかなか上手い 私はここに おっと負けた! 今回は彼が勝ちましたが また今度対戦します カピバラありがとう では最後になりますが 異なるactorシステムを組み合わることにより 何を達成できるかをご紹介します 例えば WebSocketシステムを使用して サーバ側のロビーシステムの デバイスホストのactorプレイヤ actorを登録し その間の分散呼び出しの プロキシにすることができます GameLobby actorを実装すると デバイスホストのプレイヤ actorが 自身で登録できるようになります デバイスがオンラインモードに入ると 受付を使ってGameLobbyを発見し 呼び出しが始まります GameLobbyは利用可能なプレイヤを探し プレイヤのペアが特定されると ゲームセッションを開始します ゲームセッションは推進者となり 動きをポーリングしサーバに保管された ゲーム表現に記録します ゲームが終了すると結果を収集して ロビーに報告します さらに興味深いのは この設計を水平に拡大できる点です 単一サーバでより多くの適合性を提供するために ゲームセッションactorを 増やすこともできますが 分散 actorのおかげで 他のノードでゲームセッションを作成し クラスタ全体でゲーム適合数を ロードバランスできます それはクラスタ actorシステムがある場合のみ 実はそれもあります! そのようなシナリオではオープンソース Cluster Actorシステム ライブラリを利用できます SwiftNIOで実装されサーバ側データセンタの クラスタリングに特化しています 失敗検知のための高度技術があり 自身のクラスタ全体の受付の 実装が付いてきます どちらもactorシステムの actorシステムの高度参照実装で 強力なサーバ側のAppとなりますので 是非お試しください ではまとめです 最初に分散actorについて学び 追加のコンパイラ支援のactor隔離と シリアル化検査について学びました ロケーション透過方法についてと actorを呼び出し元と 同じプロセスが行われる場所から 解放する方法を学び 分散actorを使用して何かを構築することを 奨励するためにactorシステムの実装の 実演をお見せしました 分散actorは使用されるactorシステムと 効力が同じです そのため皆さんのために actorシステムのリストを ここでお見せしましょう Swiftでデフォルトのローカルテストシステム 2つのactorシステムのサンプル クライアント/サーバ式 WebSocketベースのものと ローカルネットワーキング ベースのシステムです これらはまだ完成しておらず 分散actorを使用して何かを構築するための インスピレーションのようなものです これはこのセッションに 関連するサンプルコードAppで 確認することができます 最後にオープンソースの 完全機能のサーバ側のクラスタ実装です ベータ版として現在ご利用いただけ Swift 5.7.と共に成長していくでしょう 分散actorの詳細については 次のリソースをご覧ください このセッションで使用したサンプルコード 「Tic Tac Fish」で辿った 手順がすべて記載されており コードを詳細に確認できます 分散actor言語機能に関連する Swift evolution proposalでは メカニズムが詳細に説明されています またSwiftフォーラムにアクセスすると デベロッパやユーザに役に立つactorシステムの 分散actorのカテゴリが見つかります ご清聴ありがとうございました 皆さんのAppで分散actorが 活用されるのが楽しみです ♪
-
-
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)) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。