RustでUnityプラグインを作って敗北する

## なんか最近流行ってるらしい
浮世の変化には疎いのですが、なんか流行ってるらしいですね`Rust`。
実は`Unity`のネイティブプラグインを作ってみたかったのですが、`C`も`C++`もやったことない上に勉強する気もないため踏み切れないでいました。
いい機会なので`Rust`で作ってみます。

## 目的
- `Rust`を使ってみる
- `Unity`のネイティブプラグインを作ってみる
- 自分の学習軌道をメモしておく

## 書いている人
スマホ開発がメイン
`C`と`C++`は未経験
値渡しと参照渡しはわかるけどぽいんた？　とかいうのはわからん

## 情報取得
とりあえずインプットします。

### Rust
[Rustの日本語ドキュメント/Japanese Docs for Rust](https://doc.rust-jp.rs)
[プログラミング言語 Rust, 2nd Edition/ The Rust Programming Language, Second Edition](https://doc.rust-jp.rs/book/second-edition/foreword.html)
[プログラミング言語Rust](https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/README.html)
[The Rust Programming Language](https://doc.rust-lang.org/book/)

[必修言語Rustの他己紹介](https://tech-blog.optim.co.jp/entry/2019/01/07/173000)
[Rust についてのメモ](https://exoskeleton.dev/proglang/rust/)
[Rustのポインタ（所有権・参照）・可変性についての簡単なまとめ](https://qiita.com/nebutalab/items/1d7a03c36c087c3f6360)
[Rustはこうやって勉強するといいんじゃないか、という一例](https://qiita.com/TakaakiFuruse/items/13e9ad9d1efe7e17811c)
[Rustのクレート・ツールを探すための情報源](https://qiita.com/garkimasera/items/95235bfb1de5f3e66911)

コンパイル通るまで**殴れば**いい！　かんたん！
こんなにやさしいコンパイラ初めて見ました。`cargo`による依存関係の管理もよく練れていていい感じです。
`Kotlin`なんかもそうですが近代の言語はユーザ獲得のため、チュートリアルがおそろしく充実していていいですね。
あとプロジェクト新規作成したら`git`作ってくれるのすごい。

### NativePlugin
[Rustで実装したアルゴリズムをUnityから使う](https://qiita.com/hadashiA/items/3755786e95bbcd8f3b5d)
[C#からC++(DLL)に配列を渡す](https://www.shibuya24.info/entry/cs2cpp_array)
[How do I get Rust FFI to return array of structs or update memory?](https://www.reddit.com/r/rust/comments/d3c7be/how_do_i_get_rust_ffi_to_return_array_of_structs/)
[How to return an array of structs from Rust to C#](https://stackoverflow.com/questions/33581363/how-to-return-an-array-of-structs-from-rust-to-c-sharp)

プラグイン更新のたびに`UnityEditor`の再起動が必要とかつらい。

### エディタ
`intellij`製品じゃないともうなにも書けない。
[MEET INTELLIJ RUST](https://intellij-rust.github.io/docs/quick-start.html)


## `Vector3`を100倍して返す

ひとまず手始めとして、`C#`から渡された`Vector3`を`Rust`側で100倍して返します。

### とりあえず作る

```rust:Rust側
// こんな感じで構造体を定義
#[repr(C)]
pub struct Vector3 {
    x: f32,
    y: f32,
    z: f32,
}

// 外部公開する関数
#[no_mangle]
pub extern fn size_up(v: &Vector3) -> Vector3 {
    Vector3 { x: v.x * 100.0, y: v.y * 100.0, z: v.z * 100.0 }
}
```

```c#:C#側
[DllImport("libtest")]
private static extern Vector3 size_up(Vector3 moto);
```


さっそく実行してみましょう。

```c#:テストコード
var v3 = UnityEngine.Random.insideUnitSphere;
var sizeUpV3 = size_up(v3);
Debug.Log($"{v3} -> {sizeUpV3}");
```

```:実行結果
(-0.1, -0.8, -0.2) -> (254563.7, 0.0, 1402342000000000000000000.0)
```
……なんか……なんだろう、**よくないことが起こっている**ようですね。

### 借用をやめる

というわけで、`Rust`側を修正します。

```rust:引数を修正
#[no_mangle]
pub extern fn size_up(v: Vector3) -> Vector3 {
    Vector3 { x: v.x * 100.0, y: v.y * 100.0, z: v.z * 100.0 }
}
```

引数の`v: Vector3`をうっかり拝借していましたが、`Rust`内で完結するならばともかく`C#`から借りてくるのはあまりにも無茶な話でした。というわけでそのまま渡してみます。

```:実行結果
(-0.3, -0.7, 0.2) -> (-26.1, -66.8, 21.3)
```

……**またよくないことが**……いや、よく見たら四捨五入してそうな数字です。

### *nicely formatted*

[Rider](https://www.jetbrains.com/ja-jp/rider/)先生にデコンパイルしてもらって`Vector3`の`ToString`を覗いてみます。

```c#:Vector3.ToString
/// <summary>
///   <para>Returns a nicely formatted string for this vector.</para>
/// </summary>
/// <param name="format"></param>
public override string ToString()
{
  return UnityString.Format("({0:F1}, {1:F1}, {2:F1})", (object) this.x, (object) this.y, (object) this.z);
}
```

> nicely formatted string

${\Large なに言うとるがじゃ！！！！！}$

しょうがないのでこんな感じの拡張メソッド定義してありのままの姿を見せてもらうことにします。

```c#:ToStringFloat
static class Extensions
{
    public static string ToStringFloat(this Vector3 vector3)
    {
        return $"({vector3.x}, {vector3.y}, {vector3.z})";
    }
}
```

```c#:テストコード
var v3 = UnityEngine.Random.insideUnitSphere;
var sizeUpV3 = size_up(v3);
Debug.Log($"{v3.ToStringFloat()} -> {sizeUpV3.ToStringFloat()}");
```

```:実行結果
(-0.01608862, 0.5905958, 0.7266953) -> (-1.608862, 59.05958, 72.66953)
```

できました。

気になるのは借用をやめた修正です。
`C#`側では「もともとの`Vector3`」「引数としてコピーされた`Vector3`」の2つがあります。
「もともとの`Vector3`」は`C#`が管理しているからいいとして、`Rust`の借用ではないということは「引数としてコピーされた`Vector3`」を`Rust`側で開放しちゃってそうな気がしますが、これって`C#`側の扱いはどうなっているのでしょうか。`extern`だとそのあたり忖度されるんでしょうか。もしくは`struct`なので`C#`から渡すときに値をコピーしてるから大丈夫なのか。まあいいか。

## 計測
ようやく`Native Plugin`を使いたい理由に入ります。
`Mesh`の頂点座標を基準点からの相対位置に変換する処理ですが、この処理がやや重い……ような気がします。そう頻繁に行う処理でもないので無理に高速化する必要もないのですが、今回はやってみることそのものが目的です。
というわけで、以下の`C#`で書かれた関数を`Rust`側へ計算処理を逃がす関数にするのが今回のゴールです。

```c#:TransWithCsharp
public static Vector3[] TransWithCsharp(Matrix4x4 matrix, IReadOnlyList<Vector3> points)
{
    var ret = new Vector3[points.Count];
    for (var count = 0; count < points.Count; count++)
    {
        ret[count] = matrix.MultiplyPoint(points[count]);
    }
    return ret;

    // LINQでこう書くと実際オサレ
    // return points.Select(matrix.MultiplyPoint).ToArray();
}
```


### matrix4x4

`C#`から`Matrix4x4`を受け取るため、`Rust`側で同じ構造体を定義します。
本来ならありものを使うのではなく、`C#`側でも自分でちゃんと受け渡すための構造体を定義するべきですが、`Vector3`もそのまま渡せたんだから`Matrix4x4`も行けるやろ！　の精神です。

```rust:Matrix4x4
#[repr(C)]
pub struct Matrix4x4 {
    m00: f32,
    m01: f32,
    m02: f32,
    m03: f32,
    m10: f32,
    m11: f32,
    m12: f32,
    m13: f32,
    m20: f32,
    m21: f32,
    m22: f32,
    m23: f32,
    m30: f32,
    m31: f32,
    m32: f32,
    m33: f32,
}
```

ちゃんと`Rust`側で受け取れているか試すために、以下の関数を作って`C#`と突き合わせてみます。

```rust
#[no_mangle]
pub extern fn matrix_add(matrix: Matrix4x4) -> Vector3 {
    Vector3 { x: matrix.m00, y: matrix.m01, z: matrix.m02 }
}
```

```c#:テストコード
var Anchor = new GameObject().transform;
Anchor.position = UnityEngine.Random.insideUnitSphere;
Anchor.rotation = UnityEngine.Random.rotation;
Anchor.localScale = UnityEngine.Random.insideUnitSphere + Vector3.one;
var matrix = Anchor.transform.localToWorldMatrix;

var a = matrix_add(matrix);
Debug.Log(a.ToStringFloat());
var b = new Vector3(matrix.m00, matrix.m01, matrix.m02);
Debug.Log(b.ToStringFloat());
```

```:実行結果
(-0.9590587, -0.6367525, -0.6988028)
(-0.9590587, -0.2962521, 0.6547196)
```

最初だけ合っている。ということはつまり構造体のメンバの定義されている順番が違うのでしょう。
再び[Rider](https://www.jetbrains.com/ja-jp/rider/)先生にデコンパイルしてもらいます。

```c#
  public struct Matrix4x4 : IEquatable<Matrix4x4>
  {
    [NativeName("m_Data[0]")]
    public float m00;
    [NativeName("m_Data[1]")]
    public float m10;
    [NativeName("m_Data[2]")]
    public float m20;
    [NativeName("m_Data[3]")]
    public float m30;
    [NativeName("m_Data[4]")]
    public float m01;
    [NativeName("m_Data[5]")]
    public float m11;
    [NativeName("m_Data[6]")]
    public float m21;
    [NativeName("m_Data[7]")]
    public float m31;
    [NativeName("m_Data[8]")]
    public float m02;
    [NativeName("m_Data[9]")]
    public float m12;
    [NativeName("m_Data[10]")]
    public float m22;
    [NativeName("m_Data[11]")]
    public float m32;
    [NativeName("m_Data[12]")]
    public float m03;
    [NativeName("m_Data[13]")]
    public float m13;
    [NativeName("m_Data[14]")]
    public float m23;
    [NativeName("m_Data[15]")]
    public float m33;
  }
```

十の位から増えてるの……？
なんか感覚と違いますが、そう定義されている以上はしょうがありません。`Rust`側の構造体の定義の順番を変えます。

```rust
#[repr(C)]
pub struct Matrix4x4 {
    m00: f32,
    m10: f32,
    m20: f32,
    m30: f32,
    m01: f32,
    m11: f32,
    m21: f32,
    m31: f32,
    m02: f32,
    m12: f32,
    m22: f32,
    m32: f32,
    m03: f32,
    m13: f32,
    m23: f32,
    m33: f32,
}
```

```:実行結果
(-0.04724042, -0.6401328, 0.3618424)
(-0.04724042, -0.6401328, 0.3618424)
```

一致しました。`Matrix4x4`はちゃんと`C#`から`Rust`に渡せています。


### ダブルキャスト

```c#
/// <summary>
///   <para>Transforms a position by this matrix (generic).</para>
/// </summary>
/// <param name="point"></param>
public Vector3 MultiplyPoint(Vector3 point)
{
    Vector3 vector3;
    vector3.x = (float) ((double) this.m00 * (double) point.x + (double) this.m01 * (double) point.y + (double) this.m02 * (double) point.z) + this.m03;
    vector3.y = (float) ((double) this.m10 * (double) point.x + (double) this.m11 * (double) point.y + (double) this.m12 * (double) point.z) + this.m13;
    vector3.z = (float) ((double) this.m20 * (double) point.x + (double) this.m21 * (double) point.y + (double) this.m22 * (double) point.z) + this.m23;
    float num = 1f / ((float) ((double) this.m30 * (double) point.x + (double) this.m31 * (double) point.y + (double) this.m32 * (double) point.z) + this.m33);
    vector3.x *= num;
    vector3.y *= num;
    vector3.z *= num;
    return vector3;
}
```

肝心の`MultiplyPoint`の処理です。
デコンパイラの結果というのもあると思いますが、なかなかにカオスな計算処理。
`float -> double -> float`とキャストしている部分を`Rust`でも再現するかは悩みどころですが、いったんは心を無にして`Rust`でも同様の処理を書きます。

```rust
#[no_mangle]
pub extern fn multiply_point(m: Matrix4x4, v: Vector3) -> Vector3 {
    let x = ((m.m00 as f64 * v.x as f64 + m.m01 as f64 * v.y as f64 + m.m02 as f64 * v.z as f64) + m.m03 as f64) as f32;
    let y = ((m.m10 as f64 * v.x as f64 + m.m11 as f64 * v.y as f64 + m.m12 as f64 * v.z as f64) + m.m13 as f64) as f32;
    let z = ((m.m20 as f64 * v.x as f64 + m.m21 as f64 * v.y as f64 + m.m22 as f64 * v.z as f64) + m.m23 as f64) as f32;
    let num = (1.0 / (m.m30 as f64 * v.x as f64 + m.m31 as f64 * v.y as f64 + m.m32 as f64 * v.z as f64) + m.m33 as f64) as f32;
    Vector3 { x: (x * num), y: (y * num), z: (z * num) }
}
```

```c#:テストコード
var v3 = UnityEngine.Random.insideUnitSphere;
Anchor = new GameObject().transform;
Anchor.position = UnityEngine.Random.insideUnitSphere;
Anchor.rotation = UnityEngine.Random.rotation;
Anchor.localScale = UnityEngine.Random.insideUnitSphere + Vector3.one;
var matrix = Anchor.transform.localToWorldMatrix;

var withU = matrix.MultiplyPoint(v3);
Debug.Log(withU.ToStringFloat());

var withR = multiply_point(matrix, v3);
Debug.Log(withR.ToStringFloat());

```
```:実行結果
(-0.02336239, 0.5018276, 0.7009525)
(-Infinity, Infinity, Infinity)
```
*Infinity...*

数回繰り返したところ正負は合っているので、キャストに失敗して無限の彼方に辿り着いているようです。
こんなもんの原因追求する気はさらさらないので`Rust`のコードをきれいに書き直します。

```rust
fn multiple_float(a: f32, b: f32) -> f64 {
    ((a as f64) * (b as f64))
}

#[no_mangle]
pub extern fn multiply_point(m: Matrix4x4, v: Vector3) -> Vector3 {
    let x = multiple_float(m.m00, v.x) + multiple_float(m.m01, v.y) + multiple_float(m.m02, v.z) + m.m03 as f64;
    let y = multiple_float(m.m10, v.x) + multiple_float(m.m11, v.y) + multiple_float(m.m12, v.z) + m.m13 as f64;
    let z = multiple_float(m.m20, v.x) + multiple_float(m.m21, v.y) + multiple_float(m.m22, v.z) + m.m23 as f64;
    let a = multiple_float(m.m30, v.x) + multiple_float(m.m31, v.y) + multiple_float(m.m32, v.z) + m.m33 as f64;
    let num = 1.0 / a;
    Vector3 { x: (x * num) as f32, y: (y * num) as f32, z: (z * num) as f32 }
}
```
なんかもっときれいに書けるような、そうでもないような。
ともあれこれを実行してみます。

```:実行結果
(-0.1820646, -0.6444009, 0.6140736)
(-0.1820646, -0.6444009, 0.6140736)
```

一致しました。これでようやく完成です。


## 実験
さっそく`C#`と比べて早いのか遅いのか実験してみます。

```c#:テストコード
private const int PointCount = 1000000;

private async void Check(CancellationToken token)
{
    var anchor = new GameObject().transform;

    while (true)
    {
        anchor.position = UnityEngine.Random.insideUnitSphere;
        anchor.rotation = UnityEngine.Random.rotation;
        anchor.localScale = UnityEngine.Random.insideUnitSphere + Vector3.one;

        var matrix = anchor.transform.localToWorldMatrix;
        var randomVectors = await Task.Run(() => GenerateRandomVectorAsync(_cancellationTokenSource.Token, PointCount), token);

        // Rustによる変換
        Profiler.BeginSample("#ByRust");
        var r = TransByRust(matrix, randomVectors);
        Profiler.EndSample();

        // Rust(Releaseビルド)による変換
        Profiler.BeginSample("#ByRustR");
        var rr = TransByRustRelease(matrix, randomVectors);
        Profiler.EndSample();

        // C#による変換
        Profiler.BeginSample("#ByCSharp");
        var c = TransByCsharp(matrix, randomVectors);
        Profiler.EndSample();

        Debug.Log(r.Length + " - " + rr.Length + " - " + c.Length);
    }
}

// C#による変換
private static Vector3[] TransByCsharp(Matrix4x4 matrix, IReadOnlyList<Vector3> points)
{
    var ret = new Vector3[points.Count];
    for (var count = 0; count < points.Count; count++)
    {
        ret[count] = matrix.MultiplyPoint(points[count]);
    }

    return ret;
}

// RustDebugビルドによる変換
private static Vector3[] TransByRust(Matrix4x4 matrix, IReadOnlyList<Vector3> points)
{
    var ret = new Vector3[points.Count];
    for (var count = 0; count < points.Count; count++)
    {
        ret[count] = multiply_point(matrix, points[count]);
    }

    return ret;
}

// RustReleaseビルドによる変換
private static Vector3[] TransByRustRelease(Matrix4x4 matrix, IReadOnlyList<Vector3> points)
{
    var ret = new Vector3[points.Count];
    for (var count = 0; count < points.Count; count++)
    {
        ret[count] = multiply_point_r(matrix, points[count]);
    }

    return ret;
}

// ランダムなVector3の配列を生成
private static Task<Vector3[]> GenerateRandomVectorAsync(CancellationToken cancellationToken, int length)
{
    var random = new System.Random();
    var points = new Vector3[length];
    for (var count = 0; count < points.Length; count++)
    {
        cancellationToken.ThrowIfCancellationRequested();
        // UnityEngine.RandomのAPIはメインスレッドからしか呼べない...
        // なので無理矢理ランダムなVector3を生成する
        points[count].x = (float) (random.NextDouble() * random.Next(-100, 100));
        points[count].y = (float) (random.NextDouble() * random.Next(-100, 100));
        points[count].z = (float) (random.NextDouble() * random.Next(-100, 100));
    }

    return Task.FromResult(points);
}

```


これがプロファイラの結果です。

<img width="669" alt="スクリーンショット 2019-11-27 3.56.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146245/5d819ec3-9e61-7196-1499-f0253d2fb93f.png">

**C#が一番速い**……。
`Rust`のリリースビルドとデバッグビルドで差が出ている以上、`NativePlugin`だからプロファイラがおかしくなっているわけでもないようです。


`f32`が溢れるようなことはまずないので、`Rust`側でキャストを止めてみます。

```rust:multiply_point_without_cast
#[no_mangle]
pub extern fn multiply_point_without_cast(m: Matrix4x4, v: Vector3) -> Vector3 {
    let x = m.m00 * v.x + m.m01 * v.y + m.m02 * v.z + m.m03;
    let y = m.m10 * v.x + m.m11 * v.y + m.m12 * v.z + m.m13;
    let z = m.m20 * v.x + m.m21 * v.y + m.m22 * v.z + m.m23;
    let a = 1.0 / (m.m30 * v.x + m.m31 * v.y + m.m32 * v.z + m.m33);
    Vector3 { x: (x * a), y: (y * a), z: (z * a) }
}
```

<img width="669" alt="スクリーンショット 2019-11-29 3.33.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146245/bef1aeb0-e384-400f-7561-f1ecb74e3e4b.png">

ちょっとはやくなってる。

## 仮説
<dl>
  <dt>1. UnityのMatrix4x4.MultiplyPointはC++層で実行されている</dt>
  <dd>デコンパイルするってことはコンパイルされてるんだよねこれ</dd>
  <dt>2. C#がmatrix4x4をキャッシュしているのに対し、Rustは毎回受け渡しているため非効率</dt>
  <dd>これは確実にあるはず</dd>
  <dt>3. 言語間で受け渡すコスト > Rustによる恩恵</dt>
  <dd>単純な計算処理では意味がなかった</dd>
</dl>

仮説1が一番大きいと思います。わたしが戦っていたのはキャストしまくりの`C#`ではなくバチバチにチューニングされた`C++`だったのです……たぶん。なので自分で実装した重い処理とかだったら違う結果が出るかもしれません。

仮説２の解決として`Vector3[]`の配列を受け渡しできればいいのですが、ポインタがわからないからマーシャリングもわからないので諦めました。`Rust`側でポインタを復元する方法もわからないです。

仮説3もわりとありそうな気がしています。いちいち変換している分のコストはかなり大きい……はず。

あと、いくら`Vector3`とはいえこの数なら結構なGCを誘発していると思うのですが、プロファイラの`GC Alloc`はみんないっしょです。`NativePlugin`部分に対するプロファイラの動作もいまいち情報がないのでよくわからん。


## まとめ
### Rust
今更ですが`edition`は`2018`です。
`Rust`の学習ですが、ヤバいと噂の所有権は自分はそんなにひっかかりませんでした。でもライフタイムは微妙にまだよくわかってないかもしれない。
あと、エラー処理と並列プログラミングはちらっと読んだだけで何言ってるかまったく理解してないので改めて読もうと思います。
`Rust`の学習コストは確かに高いですが、コンパイラさんが徹底的にチェックしてくれることで実行時に吹っ飛ぶのを防止してくれるのはとても好きです。

### NativePlugin
`Unity`+`C#`+`Rust`の知識を要求されるのつらい。

### 敗北
プログラミングぢからは高まった気がしますが、結果が出せていません。
しかし現在の自分ではこれ以上は手が出ない……。ポインタを理解するために`C`を諦めてやってみるべきか……。
なにはともあれ今回はここで敗北します。誰かなんか強い人がなんとかしてくれたら嬉しいな！　サヨナラ！

とりあえず書いた分は置いておきます。
[gist](https://gist.github.com/nekomimi-daimao/59811f30f633cbd02faf647857c8f41b)