5. Component にまとめて配布する
ここまでは、Process も Container も ContainerProcess も、1 つの型ごとに別々のファイル(ライブラリ)として作ってきました。
第 3 章の設定ファイルでも、plugins に型を 1 つずつ並べて、それぞれのファイルを読み込ませていました。
実際に「このカメラを動かす部品一式」を誰かに渡すときに、ファイルがばらばらだと不便です。 この章では、Process・Container・ContainerProcess を 1 つのライブラリにまとめた Component を作り、設定ファイルから読み込みます。
サンプルを用意する
Section titled “サンプルを用意する”第 1 章でクローンしてビルドしたサンプル集をそのまま使います。Component のサンプルも、第 1 章のビルドで一緒にできあがっています。 まだの場合は、第 1 章の「サンプルを用意する」 の手順を済ませてください。
この章で使う Component には、次のものが入っています。どれも、これまでの章で作ったものと同じ働きです。
| 種類 | 働き |
|---|---|
| Process | 引数に 1 を足して返す(第 1 章の increment_process と同じ) |
| Container | 整数値を 1 つ持つ(第 2 章のカウンタと同じ) |
| ContainerProcess | Container の値を読む・変える |
Component のコードを読む
Section titled “Component のコードを読む”component/example_component/src/lib.rs
use juiz_sdk::prelude::*;
#[juiz_component_process]fn example_component_increment(arg1: i64) -> JuizResult<Capsule> { log::trace!("increment_process({:?}) called", arg1); return Ok(jvalue!(arg1 + 1).into());}
#[repr(Rust)]pub struct ExampleComponentContainer { pub value: i64,}
#[juiz_component_container]fn example_component_container(initial_value: i64) -> JuizResult<Box<ExampleComponentContainer>> { println!("example_component_container({initial_value}) called"); Ok(Box::new(ExampleComponentContainer { value: initial_value, }))}
#[juiz_component_container_process(container_type = "example_component_container")]fn example_component_container_get( container: &mut ContainerImpl<ExampleComponentContainer>,) -> JuizResult<Capsule> { println!("example_component_container_get()"); Ok(jvalue!(container.value).into())}
#[juiz_component_container_process(container_type = "example_component_container")]fn example_component_container_increment( container: &mut ContainerImpl<ExampleComponentContainer>,) -> JuizResult<Capsule> { println!("example_component_container_increment()"); container.value = container.value + 1; Ok(jvalue!(container.value).into())}
#[juiz_component_container_process( container_type = "example_component_container" arguments = { default = { arg1 = 1 } })]fn example_component_container_add( container: &mut ContainerImpl<ExampleComponentContainer>, arg1: i64,) -> JuizResult<Capsule> { println!("example_component_container_add({arg1})"); container.value = container.value + arg1; Ok(jvalue!(container.value).into())}
juiz_component_manifest!( component_name = "example_component" containers = { example_component_container = [ example_component_container_get, example_component_container_increment, example_component_container_add ] } processes = [ example_component_increment ]);関数の書き方は、これまでとほとんど同じです。違うのは 2 点だけです。
- 属性マクロが
#[juiz_process]→#[juiz_component_process]、#[juiz_container]→#[juiz_component_container]、#[juiz_container_process]→#[juiz_component_container_process]のように、componentの付いたものに変わります。 - 最後の
juiz_component_manifest!で、この Component の名前と、中に入れる Process・Container・ContainerProcess を宣言します。ContainerProcess は、結びつく Container の下に並べます。
Component 全体が 1 つのクレート(Cargo.toml の crate-type = ["cdylib"])になり、target/debug/libexample_component.so(macOS では .dylib)という 1 つの共有ライブラリにビルドされます。
component/example_component_python.py
from juiz import juiz_container, juiz_container_process, juiz_process
@juiz_processdef example_component_python_increment(arg1: int = 0) -> int: """引数に 1 を足して返す""" return arg1 + 1
class IntegerContainer: def __init__(self, value: int): self.value = value
@juiz_containerdef example_component_python_container(initial_value: int = 0) -> IntegerContainer: """整数値を 1 つ保持する Container""" return IntegerContainer(initial_value)
@juiz_container_process(container_type="example_component_python_container")def example_component_python_container_set(container, value: int = 0) -> int: """Container の値を設定する""" container.value = value return container.value
@juiz_container_process(container_type="example_component_python_container")def example_component_python_container_get(container) -> int: """Container の値を返す""" return container.value使っているデコレータは、第 1 章・第 2 章とまったく同じです。違うのは、それらを 1 つのファイルに並べたことだけです。
Component として読み込むと、このファイルの中でデコレータを付けた関数が、まとめてこの Component の型になります。ファイル名 example_component_python が Component の名前です。
component/example_component_cpp/example_component_cpp.cpp
#include "juiz/juiz.h"
// 1 つのライブラリに Process・Container・ContainerProcess をまとめた Component//// Component では各要素を COMPONENT_*_FACTORY で公開し、// 最後に component_manifest() でまとめて宣言する。
// Process: arg1 に 1 を足して返すstatic auto example_component_cpp_increment_manif = ProcessManifest("example_component_cpp_increment") .add_int_arg("arg1", "value to be incremented", 1);std::optional<int64_t> example_component_cpp_increment(int64_t arg1) { return arg1 + 1;}COMPONENT_PROCESS_FACTORY(example_component_cpp_increment_manif, example_component_cpp_increment);
// Container: 整数値を 1 つ保持するclass ComponentContainer {public: int64_t value; ComponentContainer(int64_t v) : value(v) {}};
static auto example_component_cpp_container_manif = ContainerManifest("example_component_cpp_container");ComponentContainer* example_component_cpp_container(juiz::CapsuleMap args) { int64_t initial_value = 0; if (args.has("value")) { initial_value = args.get_int("value"); } return new ComponentContainer(initial_value);}COMPONENT_CONTAINER_FACTORY(example_component_cpp_container_manif, example_component_cpp_container);
// ContainerProcess: Container の値を返すstatic auto example_component_cpp_container_get_manif = ProcessManifest("example_component_cpp_container_get");std::optional<int64_t> example_component_cpp_container_get(ComponentContainer* container) { return container->value;}COMPONENT_CONTAINER_PROCESS_FACTORY(ComponentContainer, example_component_cpp_container_get_manif, example_component_cpp_container_get);
// ContainerProcess: Container の値に arg1 を足すstatic auto example_component_cpp_container_increment_manif = ProcessManifest("example_component_cpp_container_increment") .add_int_arg("arg1", "value to be added", 1);std::optional<int64_t> example_component_cpp_container_increment(ComponentContainer* container, int64_t arg1) { container->value += arg1; return container->value;}COMPONENT_CONTAINER_PROCESS_FACTORY(ComponentContainer, example_component_cpp_container_increment_manif, example_component_cpp_container_increment);
// Component 全体のマニフェストauto component_manifest() { return ComponentManifest("example_component_cpp") .add_process(example_component_cpp_increment_manifest()) .add_container(example_component_cpp_container_manifest() .add_process(example_component_cpp_container_get_manifest()) .add_process(example_component_cpp_container_increment_manifest()));}DEFINE_COMPONENT_MANIFEST_ENTRY_POINT(component_manifest)1 つのライブラリにいくつもの型を入れるので、これまでとは書き方が少し変わります。
- 型ごとのマニフェストは、
manifest()という同じ名前の関数ではなく、example_component_cpp_increment_manifのように型ごとに名前を付けた変数に置きます。 - 公開には
PROCESS_FACTORYなどの代わりに、COMPONENT_PROCESS_FACTORY・COMPONENT_CONTAINER_FACTORY・COMPONENT_CONTAINER_PROCESS_FACTORYを使います。これらは、関数のほかに<関数名>_manifest()という関数も作ります。 - 最後の
component_manifest()で、Component の名前と、中に入れる型を宣言します。ContainerProcess はadd_container(...)の中で、結びつく Container にadd_processします。DEFINE_COMPONENT_MANIFEST_ENTRY_POINTで、これを juiz から読めるように公開します。
ビルドは component/CMakeLists.txt の 1 行です。
add_juiz_component(example_component_cpp SOURCES example_component_cpp/example_component_cpp.cpp)build/plugins/libexample_component_cpp.so(macOS では .dylib)という 1 つの共有ライブラリができます。
これまで型ごとに分かれていたものが、Component では 1 つのライブラリ(Python では 1 つのファイル)に入っています。 それぞれの Process や ContainerProcess の中身は、これまでと変わりません。Process は冪等な関数のまま、状態に触れるのは ContainerProcess だけ、という考え方もそのままです。
設定ファイルを読む
Section titled “設定ファイルを読む”name: example_componentoption: http_broker: start: true port: 8000plugins: components: example_component: language: rust path: "./target/debug"processes: - type_name: example_component_increment name: proc0containers: - type_name: example_component_container name: container0 initial_value: 100 processes: - type_name: example_component_container_increment name: increment0 - type_name: example_component_container_add name: add0 - type_name: example_component_container_get name: get0name: example_componentoption: http_broker: start: true port: 8000plugins: components: example_component_python: language: python path: "./component"processes: - type_name: example_component_python_increment name: increment0containers: - type_name: example_component_python_container name: container0 processes: - type_name: example_component_python_container_set name: set0 - type_name: example_component_python_container_get name: get0name: example_componentoption: http_broker: start: true port: 8000plugins: components: example_component_cpp: language: "c++" path: "./build/plugins"processes: - type_name: example_component_cpp_increment name: proc0containers: - type_name: example_component_cpp_container name: container0 value: 100 processes: - type_name: example_component_cpp_container_increment name: increment0 - type_name: example_component_cpp_container_get name: get0第 3 章の設定ファイルと比べてみましょう。
plugins: Component を 1 つ書く
Section titled “plugins: Component を 1 つ書く”plugins: components: example_component: path: "./target/debug"第 3 章では、process_factories と container_factories に型を 1 つずつ書き、ContainerProcess も Container の下に 1 つずつ書いていました。
Component では、components に Component の名前を 1 つ書くだけです。キーの名前から読み込むファイルが決まるのは、第 3 章と同じです。
Component 名 example_component なら、./target/debug の中の libexample_component.so が読み込まれます。
Component 名 example_component_python なら、./component の中の example_component_python.py が読み込まれます。
Component 名 example_component_cpp なら、./build/plugins の中の libexample_component_cpp.so が読み込まれます。
この 1 行で、Component の中にある Process・Container・ContainerProcess の型が、すべて読み込まれます。
processes と containers: いつもどおり作る
Section titled “processes と containers: いつもどおり作る”processes と containers の書き方は、第 3 章とまったく同じです。type_name に Component の中の型名を書き、name にインスタンスの名前を書きます。
Container の初期値を渡せるのも同じです(Python のサンプルでは省略していて、初期値 0 から始まります)。
型がどのファイルから来たのかは、インスタンスを作る側には関係ありません。 Component は「型をまとめて配る」ためのもので、作ったインスタンスは、別々のファイルから読み込んだときと同じように動きます。
設定ファイルを -f で渡して起動します。どの言語でも同じコマンドです。
juiz -f example_component.conf -dPython の場合は、第 1 章で作った venv を有効にしたターミナルで実行してください。
外から呼び出す
Section titled “外から呼び出す”juiz を起動したまま別のターミナルを開き、一覧を見てみます。
juiz process list# => ["http://127.0.0.1:8000/process/proc0::example_component_increment"]juiz container list# => ["core://core/container/container0::example_component_container"]juiz container-process list# => ["core://core/container_process/add0::example_component_container_add:container0", "core://core/container_process/get0::example_component_container_get:container0", "core://core/container_process/increment0::example_component_container_increment:container0"]juiz process list# => ["http://127.0.0.1:8000/process/increment0::example_component_python_increment"]juiz container list# => ["core://core/container/container0::example_component_python_container"]juiz container-process list# => ["core://core/container_process/set0::example_component_python_container_set:container0", "core://core/container_process/get0::example_component_python_container_get:container0"]juiz process list# => ["http://127.0.0.1:8000/process/proc0::example_component_cpp_increment"]juiz container list# => ["core://core/container/container0::example_component_cpp_container"]juiz container-process list# => ["core://core/container_process/get0::example_component_cpp_container_get:container0", "core://core/container_process/increment0::example_component_cpp_container_increment:container0"]1 つのファイルから読み込んだ型で、Process・Container・ContainerProcess のインスタンスがそろってできています。ID の形も、これまでの章とまったく同じです。 それぞれを呼び出してみましょう。
juiz process call "http://127.0.0.1:8000/process/proc0::example_component_increment" '{"arg1": 3}' -p# => CapsulePtr((value:Value(4), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/get0::example_component_container_get:container0" '{}' -p# => CapsulePtr((value:Value(100), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/increment0::example_component_container_increment:container0" '{}' -p# => CapsulePtr((value:Value(101), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/add0::example_component_container_add:container0" '{"arg1": 5}' -p# => CapsulePtr((value:Value(106), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/get0::example_component_container_get:container0" '{}' -p# => CapsulePtr((value:Value(106), option: []))juiz process call "http://127.0.0.1:8000/process/increment0::example_component_python_increment" '{"arg1": 3}' -p# => CapsulePtr((value:Value(4), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/set0::example_component_python_container_set:container0" '{"value": 10}' -p# => CapsulePtr((value:Value(10), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/get0::example_component_python_container_get:container0" '{}' -p# => CapsulePtr((value:Value(10), option: []))juiz process call "http://127.0.0.1:8000/process/proc0::example_component_cpp_increment" '{"arg1": 3}' -p# => CapsulePtr((value:Value(4), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/get0::example_component_cpp_container_get:container0" '{}' -p# => CapsulePtr((value:Value(100), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/increment0::example_component_cpp_container_increment:container0" '{"arg1": 5}' -p# => CapsulePtr((value:Value(105), option: []))juiz container-process call "http://127.0.0.1:8000/container_process/get0::example_component_cpp_container_get:container0" '{}' -p# => CapsulePtr((value:Value(105), option: []))Process は引数に 1 を足して返し、ContainerProcess は Container の値を変えたり読んだりします。 呼び出す側から見ると、第 1 章〜第 3 章で別々のファイルから作ったものと区別がつきません。
Component は配布の単位
Section titled “Component は配布の単位”ここまでで見たように、Component にまとめても、Process や Container の働きや使い方は何も変わりません。変わるのはファイルの束ね方だけです。
- 作る人は、関連する Process・Container・ContainerProcess を 1 つのプロジェクトで書き、1 つのライブラリとして配れます。たとえばカメラなら、カメラを開いて持っておく Container、画像を読む ContainerProcess、画像を変換する Process を 1 つの Component にできます。
- 使う人は、
pluginsのcomponentsに 1 行書くだけで、中の型を全部使えるようになります。どの型を何個作るかは、これまでどおりprocessesとcontainersで自由に決められます。
つまり、Process・Container・ContainerProcess が「動かすときの単位」であるのに対して、Component は「配るときの単位」です。
やってみよう
Section titled “やってみよう”processesにexample_component_increment(Python ならexample_component_python_increment、C++ ならexample_component_cpp_increment)のインスタンスをもう 1 つ書き足してみましょう。Component から読み込んだ型からも、第 3 章と同じように、いくつでもインスタンスを作れます。containersにもう 1 つ Container(たとえばcontainer1)と、それに結びつく ContainerProcess を書き足してみましょう。container0とcontainer1は別々の値を持ちます。- 第 4 章の
connectionsとecsを書き足して、1 つ目の Process の出力を、書き足した 2 つ目の Process のarg1につないでみましょう。Component から読み込んだ型かどうかに関係なく、同じようにつなげます。pluginsにはcomponentsとprocess_factoriesを並べて書けるので、第 3 章のincrement_processのような別ファイルの Process とつなぐこともできます。 - 設定ファイルを使わずに、コマンドラインから Component を読み込むこともできます。
-1を付けると、Component の中のすべての型から、インスタンスを 1 つずつ作ります(名前は第 1 章と同じく「型名 +0」になります)。起動時にERRORの行がいくつか表示されますが、そのまま動きます。
juiz --component ./target/debug/libexample_component.so -1 -djuiz --component ./component/example_component_python.py -l python -1 -djuiz --component ./build/plugins/libexample_component_cpp.so -l cpp -1 -d- Component は、複数の Process・Container・ContainerProcess を 1 つのライブラリ(Python では 1 つのファイル)にまとめたものです。
- 設定ファイルでは、
pluginsのcomponentsに Component の名前を 1 つ書くだけで、中の型がすべて読み込まれます。 - インスタンスの作り方、ID、呼び出し方、つなぎ方は、別々のファイルから読み込んだときと同じです。Component は動かし方を変えるものではなく、配布の単位です。
次の章では、別々に起動した juiz どうしを、設定ファイルなしでコマンドからつなぎます。別々のプロセスをつなぐ に進みましょう。