|
| 1 | + |
| 2 | +# パフォーマンス |
| 3 | + |
| 4 | +TASauria の構造のため、 TASauria スクリプトを作成するときに性能低下の可能性が多くあります。 |
| 5 | + |
| 6 | +典型的な TAS シナリオでは、これはあまり大きな問題ではありません。TAS を生成するのにゲームをフルスピードで実行するよりもかなり長い時間がかかると予想されます。しかし、開発段階では特に、スクリプト実行中に時間を失わないようにすることが有利です。 |
| 7 | + |
| 8 | +以下は、典型的なリクエスト(例えば `await emu.readrange(0x1000)`)が実行されたときに何が起こるかを示す図です。 |
| 9 | + |
| 10 | +```mermaid |
| 11 | +sequenceDiagram |
| 12 | +box rgba(255,128,0,0.25) Python |
| 13 | +participant PY as tasauria モジュール |
| 14 | +end |
| 15 | +box rgba(0,128,255,0.25) EmuHawk プロセス |
| 16 | +participant SR as TASauria サーバー<br/>(ワーカースレッド) |
| 17 | +participant EX as TASauria 外部ツール<br/>(メインスレッド) |
| 18 | +participant CL as EmuHawk 自体<br/>(メインスレッド) |
| 19 | +end |
| 20 | +Note over PY: 要求を<br/>シリアライズ |
| 21 | +PY->>SR: 要求を送信 |
| 22 | +Note over SR: 要求を<br/>デシリアライズ |
| 23 | +Note over SR: Validate command<br/>and security |
| 24 | +SR-->>EX: コマンドと<br/>セキュリティ条件<br/>を検証 |
| 25 | +Note over CL: 未完成のフレーム後の<br/>更新を実行<br/>(例:インタフェースを更新) |
| 26 | +Note over CL: フレーム前の<br/>更新を実行<br/>(例:入力を読み込む) |
| 27 | +CL->>EX: 外部ツールを更新 |
| 28 | +Note over EX: キューされたコマンドを取得 |
| 29 | +Note over EX: キューされたコマンドを実行する |
| 30 | +EX->>SR: コマンドの出力を送信 |
| 31 | +EX->>CL: エミュレータに制御を戻す |
| 32 | +Note over SR: 応答を<br/>シリアライズ |
| 33 | +SR->>PY: 応答を送信 |
| 34 | +Note over PY: 応答を<br/>デシリアライズ |
| 35 | +``` |
| 36 | + |
| 37 | +## スピードアップの方法 |
| 38 | + |
| 39 | +以下に対処できる遅延が多くあります: |
| 40 | + |
| 41 | +### 1. `tasauria` モジュールは、フレーム処理が終了するまで待つ必要があります。 |
| 42 | + |
| 43 | +プラグインのすべての作業が完了するまで待つ必要があるため、通常通りにコマンドを実行すると Python 側で長い待機時間になります。 |
| 44 | + |
| 45 | +このため、`tasauria` モジュールは `asyncio` を使用して実装されています。非同期機能を使って、例えば 2 つの別々のエミュレータから同時に情報を取得できる例を示します: |
| 46 | + |
| 47 | +```python |
| 48 | +buffer_1, buffer_2 = await asyncio.gather( |
| 49 | + emulator_1.readrange(0x1000, domain='RDRAM'), |
| 50 | + emulator_2.readrange(0x1000, domain='RDRAM'), |
| 51 | +) |
| 52 | +``` |
| 53 | + |
| 54 | +asyncio の動作については、 [Python ドキュメント](https://docs.python.org/ja/3/library/asyncio.html) でさらに詳しく読むことができますが、実質的には最初のリクエストが送信され、Python が最初のレスポンスを待っている間に 2 番目のリクエストが送信されます。 |
| 55 | + |
| 56 | +つまり、2 つの別々のリクエストの時間を待つ代わりに、2 つのうち遅い方の実行に必要な時間だけを待つことになります。 |
| 57 | + |
| 58 | +### 2. モジュールとサーバー間の通信に時間がかかります。 |
| 59 | + |
| 60 | +ハンドシェイクとリクエストの通信プロセスが完了するまでに時間がかかることがあります。 |
| 61 | + |
| 62 | +このため、TASauria は HTTP と WebSocket の両方のサーバーを公開しています。 |
| 63 | +WebSocket サーバーを使用すると、永続的な接続を保持し再利用できるため、毎回接続を確立するためにかかる時間を節約できます。 |
| 64 | + |
| 65 | +`TASauria` を引数なしでインスタンス化すると、デフォルトで WebSocket サーバーが使用されます: |
| 66 | + |
| 67 | +```python |
| 68 | +from tasauria import TASauria |
| 69 | + |
| 70 | +emu = TASauria() |
| 71 | +# 以下と同じです |
| 72 | +emu = TASauria("ws://127.0.0.1:20251/websocket") |
| 73 | +``` |
| 74 | + |
| 75 | +ただし、カスタムホストやポートを指定した場合は、パスを明示的に指定する必要があります。 |
| 76 | + |
| 77 | +TASauria はプロトコルから WebSocket 接続を使用すべきことを自動的に検出するため、コンストラクタでそのように指定するだけで済みます: |
| 78 | + |
| 79 | +```python |
| 80 | +emu = TASauria("http://192.168.1.50:30000/") # [!code --] |
| 81 | +emu = TASauria("ws://192.168.1.50:30000/websocket") # [!code ++] |
| 82 | +``` |
| 83 | + |
| 84 | +### 3. エミュレータのメインスレッドがリクエストを受け付ける準備ができるまで待つのに時間がかかります。 |
| 85 | + |
| 86 | +TASauria サーバーは個々のリクエスト用にワーカースレッドを生成し、TASauria 外部ツールが制御を受け取ると、可能な限り多くのキューされたコマンドを消費して実行します。 |
| 87 | + |
| 88 | +つまり、上記の他の速度アップ手法を使用すると、メインスレッドがコマンドを実行するために同期する回数を減らすことができます。 |
| 89 | + |
| 90 | +ただし、TASauria にはコマンドを一括処理する組み込み機能もあり、1 つのリクエストで複数の処理を行うことが可能です。 |
| 91 | + |
| 92 | +一部の高レベル関数はこの機能を直接公開しています (例えば、`emu.readstructs`): |
| 93 | + |
| 94 | +```python |
| 95 | +( |
| 96 | + (player_x, player_y, player_z), |
| 97 | + (camera_x, camera_y, camera_z), |
| 98 | +) = await emu.readstructs([ |
| 99 | + (0x10F1B0, ">fff"), |
| 100 | + (0x0B1D9C, ">fff") |
| 101 | +], domain='RDRAM') |
| 102 | +``` |
| 103 | + |
| 104 | +ただし、手間を惜しまない場合は、`emu.batch_commands` を使って自分でコマンドバッチングを行うこともできます。 |
| 105 | + |
| 106 | +[HTTP/WS ドキュメント](../http-ws-api/general) とおそらくモジュールのソースコードを読む必要がありますが、以下に例を示します |
| 107 | + |
| 108 | +```python |
| 109 | +from tasauria import TASauria |
| 110 | +from tasauria.commands.client import ClientFrameStatusCommand, ClientGameCommand |
| 111 | + |
| 112 | +async with TASauria() as emu: |
| 113 | + (game_info, frame_status) = await emu.batch_commands([ |
| 114 | + (ClientGameCommand, {}), |
| 115 | + (ClientFrameStatusCommand, {}), |
| 116 | + ]) |
| 117 | + |
| 118 | + print(game_info) |
| 119 | + # => GameInfo(loaded=True, name='Zelda no Densetsu - Kamigami no Triforce (Japan)', system='SNES', board_type='LOROM-RAM#A', region='', display_type='NTSC', hash='E7E852F0159CE612E3911164878A9B08B3CB9060', in_database=True, database_status='GoodDump', database_status_bad=False, game_options={}) |
| 120 | + print(frame_status) |
| 121 | + # => FrameStatus(cycle_count=229021140, frame_count=641, lag_count=281, is_lagged=False) |
| 122 | + |
| 123 | +``` |
| 124 | + |
| 125 | +バッチ化されたコマンドは、エミュレータが停止していない場合でも、手動でフレームを進めるコマンドを使用しない限り、同じフレームで実行されることが保証されています。 |
| 126 | + |
| 127 | +さらに、コマンドは順序通りに実行されるため、`MemoryReadRangeCommand`、次に `ClientFrameAdvanceCommand`、そして `MemoryReadRangeCommand` の順で実行すると、最初の読み取りは現在のフレームで、2 回目の読み取りは次のフレームで行われます。 |
| 128 | + |
| 129 | +この方法の注意点は、指定されたコマンドのいずれかが見つからない(Python 側からは実行が難しい)場合、またはセキュリティに失敗する(すなわち権限が必要で、TASauria 外部ツールウィンドウで確認されていない)場合、コマンドは実行されません。 |
| 130 | +これはバッチ処理を原子性にし、スクリプトが曖昧な状態になるのを防ぐためです。 |
| 131 | + |
| 132 | +## どうしてもダメな時 |
| 133 | + |
| 134 | +実行速度に絶対に依存する作業を行っている場合は、TASauria 以外の手段を試す時期かもしれません。 |
| 135 | + |
| 136 | +次のセクションで [代替](alternatives) を参照してください。 |
0 commit comments