こんにちは。ちょうど今年の2月までは制作会社でTouchDeisgnerやUnity,UE4などのコンテンツ制作をしておりました。フリーランスになってからTouchDeisgnerの仕事をする機会は減りましたが、去年に引き続き今年も何か投稿しようということで、以前にTouchDesigner forumで投稿したasyncioの使い方について説明したいと思います。思いのほかフォーラムでレスポンスをいただいたのでみなさんのお役に立てればと思います。
はじめに
TouchDesignerはシングルスレッドで動作するため、待機処理やI/Oバウンドな処理をpythonで実行するとTouchDesignerのプロセスは停止します。
次のようなコードを Textport または text DAT で実行してみると、指定秒数が経過するまでTouchDesignerは停止します。
import time
time.sleep(1)
HTTPによるPOSTも同様に処理が完了するまでTouchDesignerは停止したままです。
import requests
requests.post('<https://derivative.ca/>')
TouchDesignerのオペレーターにはtimer CHOPやWeb DATのような便利な機能がいくつかありますが、これらの機能をPythonで実現するためには非同期処理が必要になります。
マルチスレッドによる非同期処理
Pythonでマルチスレッドというとthreading
, concurrent.futures
, asyncio
といったライブラリがあります。中でもasyncioはシングルスレッドで動作するためTouchDesignerとの連携が簡単です。(一概にthreadingやconcurrent.futuresが適していないという訳ではないです)
ということで次のようなコードを実行したところまんまとTouchDesignerがフリーズしました..
import time
import asyncio
async def test():
await asyncio.sleep(1)
print('hello world')
asyncio.run(test())
どうやらはasyncio.run()
を実行すると内部的ではrun_until_complete()
を呼び出しており、asyncioのイベントループは実行された処理が完了するまでブロックするようです。
そこでloop_create_task()
からTaskを生成し、call_soon(event_loop.stop())
とrun_forever()
を毎フレーム実行することで、「フレーム毎にイベントループを起動してちょっと仕事したら停止する」みたいに繰り返すことでブロックを回避できるようです。。
(このあたりの説明はわからなくても使えます)
TDAsyncIO.tox
ということでそれらをまとめてtoxにしたものがこれです。
使い方は簡単でこんな感じです
import asyncio
async def test():
await asyncio.sleep(3)
print('hello world')
# Run coroutine
coroutines = [test()]
op.TDAsyncIO.Run(coroutines)
# Cancel all tasks
op.TDAsyncIO.Cancel()
グローバルショートカットを設定しているのでop.TDAsyncIO
からメソッドを呼べます。
-
op.TDAsyncIO.Run()
の引数にコルーチンのリストを渡すだけです。 -
op.TDAsyncIO.Cancel()
からすべてのタスクを停止します。
githubのtest.toeにはその他の使い方(subprocessやrequestsと合わせて使う方法)についても簡単なサンプルを置いてあります。
ぜひサーバーと連携する際などは試しに使ってもらえればと思います。
まとめ
- TouchDesignerのプロセスを停止することなくasyncioが実行できるtoxを作りました
- サーバーとの連携が必要な処理を実行したいときに便利です
- メインスレッドで実行されているためtd Moduleを使えます
ちなみに非同期処理と記載がありますがasyncioではCPUバウンドな処理を高速化することは期待できません。CPUバウンドな処理についてはEngine COMP や 刻みTD などのようにプロセスを分離することを推奨します。