4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Houdini ApprenticeAdvent Calendar 2024

Day 20

TOPsでRustを動かす

Last updated at Posted at 2024-12-19

0. はじめに

 この記事は Houdini Apprentice Advent Calendar 2024 の20日目の記事です。

0-1. 動作環境

OS: RedHat Enterprise Linux 9.4
Houdini: 20.5.410 Py3.11 (最新の Production Build) Education Edition
Rust: rustc 1.82.0

0-2. 免責

 この記事をもとに被った不利益について私は一切責任を負いません.
 あと RHEL 以外での検証もしてないです。

1. モチベーション

 TOPs って Python 書けるじゃないですか。Rust も書けたら面白いな〜って思っただけです。

2. 実装

2-1. ゴール

今回作るものです。
image.png
 パラメータに Rust のコードを入力して cook するとワークアイテムの数だけ処理が走り、 stdout アトリビュートに標準出力を返します。

2-2. 戦略

 TOPsでは Python が使えますから、

  1. パラメータの文字列から .rs ファイル(Rust の拡張子です。Redshift じゃないよ)を作る。
  2. .rs ファイルをビルドする。
  3. できたバイナリを実行する。

この3つが Python からできれば勝ちということです。
 ただし、実行は Out of Process cooking で行うので、パラメータの評価などHoudiniにアクセスすることができません。
 Out of Process cooking では json ファイルを介してプロセス間通信をおこない、ワークアイテムのアトリビュートなどのデータにはアクセスできるので、パラメータの文字列をアトリビュートに移してやり取りすることにします。

 1. に関しては $PDG_TEMP に適当なディレクトリを切ってアトリビュートの文字列から .rs ファイルを作ります。

 2, 3. は Python の Subprocess を使って rustc とバイナリを叩きます。

2-3. パラメータの文字列をアトリビュートに移す

 以下が Python スクリプトです。
 読むとわかりますが、1つ上の階層の subnet のパラメータを見てます。

import hou
# annotation のため。別にいらない
from hou import Node
from pdg import AttributeInt

#この Python Script ノードを持つ subnet を表現する Node オブジェクト
HDA_node: Node = hou.pwd().parent()

#snippet パラメータを評価して文字列として受け取る
#subnet に snippet という名前の String タイプのパラメータを作る必要があるよ
source_code: str = HDA_node.evalParm("./snippet")

#ワークアイテムにアトリビュートを追加してアトリビュートオブジェクトを受け取る
snippet_attrib: AttributeInt = work_item.addAttrib("snippet", pdg.attribType.String)
#アトリビュートの値をプログラムの文字列にする
snippet_attrib.setValue(source_code, index = 0)

 コメントにも書いてますが subnet に snippet という名前の String 型のパラメータが必要です。
 Multi-line String にチェックいれて Lines to Show を変えると縦に長くできます。
image.png

 cook するとプログラムの全文が snippet アトリビュートに書き込まれています。
image.png

2-4. ビルドして実行する

 まずは $PDG_TEMP ディレクトリに Rust というディレクトリを作成し .rs ファイルを作成します。

#PDG_TEMP環境変数を取得する
PDG_temp_dir: str = os.getenv('PDG_TEMP')
#ソースファイルを格納するディレクトリ(つまり $PDG_TEMP/Rust/src)
rust_src_dir: str = os.path.join(PDG_temp_dir, 'Rust', 'src')
#ソースファイル自体のパス(つまり $PDG_TEMP/Rust/src/main.rs)
rs_path = os.path.join(rust_src_dir, 'main.rs')
#バイナリのパス(つまり $PDG_TEMP/Rust/src/main)
binary_path = os.path.join(rust_src_dir, 'main')

#ディレクトリを作る
os.makedirs(rust_src_dir, exist_ok=True)

#ワークアイテムを取得
#out of process cooking では pdgjson.WorkItem.fromJobEnvironment() をコールすることでWorkItem オブジェクトを取得できる
work_item: WorkItem = WorkItem.fromJobEnvironment()
#ワークアイテムのアトリビュートからソースコードを取得
snippet: str = work_item.attribValue('snippet')

#ソースファイルを作る
with open(rs_path, 'w') as file:
    file.write(snippet)

 実行するとこういう感じのディレクトリ構造になります。
image.png

 次にビルドします。

try:
    #subprocessを使ってrustcを叩く
    #-oでバイナリの出力先を指定できる
    subprocess.run(['rustc', rs_path, '-o', binary_path], check = True)
except subprocess.CalledProcessError as e:
    print(f"Error during rustc compilation: {e}")
    raise

 最後に実行します。

try:
    #capture_output=True でプロセスの標準出力が取れる
    #text=True でバイナリではなく文字列の出力が取れる
    #.stdout で文字列の標準出力を返す
    stdout: str = subprocess.run([binary_path], check = True, capture_output = True, text = True).stdout
    #ワークアイテムにstdoutアトリビュートを作成し、アトリビュートオブジェクトを返す
    output_attrib: AttributeString = work_item.addAttrib("stdout", pdg.attribType.String)
    #アトリビュートの値を標準出力の文字列にする
    output_attrib.setValue(stdout)
    
except subprocess.CalledProcessError as e:
    print(f"Error during binary execution: {e}")
    raise

 以下がスクリプト全体です。

この Python Script は Out-Of-Process です。

import os
import subprocess

import hou
#annotation のためなので別にいらない。
from hou import Node
from pdgjson import WorkItem
from pdgjson import AttributeString


PDG_temp_dir: str = os.getenv('PDG_TEMP')
rust_src_dir: str = os.path.join(PDG_temp_dir, 'Rust', 'src')
rs_path = os.path.join(rust_src_dir, 'main.rs')
binary_path = os.path.join(rust_src_dir, 'main')

os.makedirs(rust_src_dir, exist_ok=True)

work_item: WorkItem = WorkItem.fromJobEnvironment()
snippet: str = work_item.attribValue('snippet')

with open(rs_path, 'w') as file:
    file.write(snippet)

    
try:
    subprocess.run(['rustc', rs_path, '-o', binary_path], check=True)
except subprocess.CalledProcessError as e:
    print(f"Error during rustc compilation: {e}")
    raise


try:
    stdout: str = subprocess.run([binary_path], check=True, capture_output=True, text = True).stdout
    output_attrib: AttributeString = work_item.addAttrib("stdout", pdg.attribType.String)
    output_attrib.setValue(stdout)
    
except subprocess.CalledProcessError as e:
    print(f"Error during binary execution: {e}")
    raise

 cook するときちんと stdout アトリビュートに出力が格納されていると思います。
image.png

 最後に attribute delete で不要な snippet アトリビュートを消しておきます。
image.png

3. おわりに

 誰得なんだという記事ですが、最近遊んでたものを紹介してみました。
 pdgjson を操作するクレートを作れば Rust からワークアイテムをいじれるのかも...?
 あとシンタックスハイライトも入れられたらいいですね。

 最後まで読んでいただきありがとうございました!

4. 参考

Job API
pdg package
Out-Of-Processの基本を理解する(1)

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?