見出し画像

哲学対話イベントで使える Udon ギミックを作った話 #4 - マルチスレッド

こちらに記事を移行しました。

前回:

長時間かかる処理を実行する際、軽量化のために別スレッドで処理を行いたいと考えました。しかし、UdonSharp は基本的にシングルスレッドで動作するため、マルチスレッド処理を直接的に実装することはできません。そこで、AudioFilterを利用して別スレッドを動かす方法を採用しました。音声の処理は画面の描画とは別のスレッドで実行されるため、この仕組みを活用することが可能です。

この方法については、【VRChat】Udonでマルチスレッド処理を行う - ku6draVR を参考にしました。以下の説明はこのサイトからの引用が多く含まれています。詳しく知りたい方や実装を試みる方は、原典を参照されることをお勧めします。

Controller と Worker の用意

まず、ControllerWorkerの 2 つのスクリプトを用意します。Worker スクリプトは無効化された空の Game Object に貼り付け、Audio Source コンポーネントを追加しておきます。

Controller スクリプトの実装

Controller スクリプトでは、Worker を実行する関数を定義します。

/// <summary>
/// Workerの実行を開始する関数
/// </summary>
public void WorkerExecute()
{
    if (_worker.enabled)
    {
        // Workerへ必要なデータを渡す
        _worker._Initialize(渡したい引数);
        _isWorkerRunning = _worker.IsRunning;

        // GameObject(またはAudioSource)を有効化し、OnAudioFilterRead()を実行させる
        // 実行中はWorkerに触らないこと
        _worker.gameObject.SetActive(true);
        SendCustomEventDelayedFrames(nameof(_CheckWorkerState), 4);
    }
}

次に、Worker の終了を監視する関数を用意します。

/// <summary>
/// Workerの終了を監視する関数
/// </summary>
public void _CheckWorkerState()
{
    if (_isWorkerRunning[0])
    {
        SendCustomEventDelayedFrames(nameof(_CheckWorkerState), 4);
    }
    else
    {
        // 処理終了後はOnAudioFilterRead()が実行されないようにする
        _worker.gameObject.SetActive(false);
        _OnWorkerComplete();
    }
}

Worker が終了した際の処理も定義します。

/// <summary>
/// Worker終了後の処理
/// </summary>
public void _OnWorkerComplete()
{
    // Workerが終了した後の処理をここに書く
}

Worker スクリプトの実装

Worker スクリプトでは、初期化処理を行います。

public void _Initialize(渡したい引数)
{
    // 実行前の処理をここに書く
    // 実行フラグを立てる
    IsRunning[0] = true;
}

次に、Audio Thread を用意します。

// Audio Thread
public void _onAudioFilterRead()
{
    OnAudioFilterRead(null, 0);
}

private void OnAudioFilterRead(float[] _, int __)
{
    if (IsRunning[0])
    {
        _stopwatch.Restart();

        bool condition = true;
        while (condition)
        {
            // ここに時間のかかるループ処理を書く
            // ループ全体を一度に実行せず、一部だけを実行し、定期的に(数ミリ秒ごとが望ましい)経過時間を確認する
            // 一定時間経過したら中断
            if (_stopwatch.ElapsedMilliseconds >= 17)
            {
                condition = false;
            }
        }

        // 処理が完了したかチェック
        if (/* 処理完了条件 */)
        {
            // ループ内で使用した配列の解放など
            IsRunning[0] = false;
        }
    }
}

注意点

  • 実行中は Worker に触らない:実行中に Worker に触れると不具合が生じる可能性があります。データのやり取りをする場合は、配列の参照を経由してください。

  • Editor での動作:Unity Editor で再生中に UdonSharpBehaviour コンポーネントが付いた Game Object を選択しないようにしてください。選択すると壊れる恐れがあります。

  • Audio Thread の占有:Audio Thread を長時間占有しないように注意してください。長時間占有すると、その間の全ての音声出力が途切れてしまいます。

次回予告

次回は軽量化の続きを、特に斥力のランダムスキップと疑似乱数について扱います。


いいなと思ったら応援しよう!