初めに
形態素解析で有名なものとして、Mecabがありますがより高速に動く Rust版のVibratoを以下の記事で動かしてみました。今回は、それをUnity上で動かしていきます。
今回の記事のUnityプロジェクトは、以下のリポジトリで公開しています
Rust側は以下になります
開発環境
- Mac (M1)
- Unity 2022.3.4f1
UnityでRustのライブラリを動かす方法について
RustのコードをUnityで動かすには,プラグイン化してUnity側からRustのコード(C言語)を呼ぶ必要があります。 今回は Apple silicon向けにビルドをしたRustのプラグインをUnityで動かしていきます
Vibratoをプラグイン化
まずはプラグイン用にRustの形態素解析のコードを作っていきます
use std::ffi::{CStr, CString}; use std::os::raw::c_char; use vibrato::{Dictionary, Tokenizer}; #[no_mangle] pub extern "C" fn tokenize(input: *const c_char, dict_path: *const c_char) -> *mut c_char { let input_str = unsafe { CStr::from_ptr(input).to_str().unwrap() }; let dict_path_str = unsafe { CStr::from_ptr(dict_path).to_str().unwrap() }; // 辞書ファイルのロード let reader = zstd::Decoder::new(std::fs::File::open(dict_path_str).unwrap()).unwrap(); let dict = Dictionary::read(reader).unwrap(); // トークナイザーの生成 let tokenizer = Tokenizer::new(dict) .ignore_space(true).unwrap() .max_grouping_len(24); let mut worker = tokenizer.new_worker(); worker.reset_sentence(input_str); worker.tokenize(); let result: String = worker.token_iter() .filter(|t| { let words: Vec<&str> = t.feature().split(',').collect(); let subwords: Vec<&str> = words[0].split('-').collect(); subwords[0] == "名詞" || subwords[0] == "カスタム名詞" }) .map(|t| format!("{}: {}", t.surface(), t.feature())) .collect::<Vec<String>>() .join("\n"); CString::new(result).unwrap().into_raw() } #[no_mangle] pub extern "C" fn free_string(s: *mut c_char) { unsafe { if s.is_null() { return } drop(CString::from_raw(s)); }; }
またRust側の Corgo.toml
は以下のように定義しています
[package] name = "rust_tokenizer" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] vibrato = "0.5.0" zstd = "0.12.3"
こちらを以下でビルドをします
cargo build --release --target aarch64-apple-darwin
これによって Apple silicon向けのビルドバイナリが以下のように作成されます.
Unity側でVibratoを呼び出す
Unity側では,ビルドをした 際に生成された librust_tokenizer.dylib
を プラグインフォルダの中に入れます。
また ipadic-mecab-2_7_0の 辞書ファイルを ipadic-mecab-2_7_0というフォルダ名を作ってその中に入れます。
次にUnity側からバイナリを呼ぶためのコードを作成します。
using System.Runtime.InteropServices; using UnityEngine; using System; using System.IO; public class RustTokenizer : MonoBehaviour { [DllImport("__Internal")] private static extern IntPtr tokenize(string input, string dictPath); [DllImport("__Internal")] private static extern void free_string(IntPtr str); [DllImport("libdl.dylib")] private static extern IntPtr dlopen(string fileName, int flags); [DllImport("libdl.dylib")] private static extern IntPtr dlerror(); void Start() { string libraryPath = Path.Combine(Application.dataPath, "Plugins", "librust_tokenizer.dylib"); Debug.Log("Attempting to load library from: " + libraryPath); IntPtr lib = dlopen(libraryPath, 2); // RTLD_NOW = 2 if (lib == IntPtr.Zero) { IntPtr errPtr = dlerror(); string errorMessage = Marshal.PtrToStringAnsi(errPtr); Debug.LogError("Failed to load library. Error: " + errorMessage); return; } Debug.Log("Library loaded successfully"); string input = "本とカレーの街神保町へようこそ。"; string dictPath = Application.dataPath + "/Plugins/ipadic-mecab-2_7_0/system.dic.zst"; IntPtr resultPtr = tokenize(input, dictPath); string result = Marshal.PtrToStringAnsi(resultPtr); free_string(resultPtr); Debug.Log(result); } }
こちらを適当なオブジェクトにアタッチすれば,本とカレーの街神保町へようこそ。
に対する形態素解析が実行されます。
本: 名詞,一般,*,*,*,*,本,ホン,ホン カレー: 名詞,固有名詞,地域,一般,*,*,カレー,カレー,カレー 街: 名詞,一般,*,*,*,*,街,マチ,マチ 神保: 名詞,固有名詞,地域,一般,*,*,神保,ジンボウ,ジンボー 町: 名詞,接尾,地域,*,*,*,町,マチ,マチ UnityEngine.Debug:Log (object) RustTokenizer:Start () (at Assets/Scripts/RustTokenizer.cs:43)