TAKOYAKING’s blog 一覧

TAKOYAKING’s blog

たこ焼き系

Rust: i8のキャストと2の補数

型キャスティング | Rust by Example
このサイトで型キャスティングを学びました。
その時 2の補数で型のキャスティングが行われると符号が反転する場合があることを知りましたので、まとめてみます。

現象

// 符号付きの型にキャストする場合、結果は以下の2つを行った場合に等しい
// 1. 対応する符号なしの型にキャストする。
// 2. 2の補数(two's complement)をとる

// 128をu8にキャストすると128となる。128の8ビットにおける補数は -128
println!(" 128 as a i8 is : {}", 128 as i8);

128をi8でキャストすると-128になります。
これはRustが符号付きでは以下のルールでキャストするためです。

符号付きの型にキャストする場合、結果は以下の2つを行った場合に等しい
1. 対応する符号なしの型にキャストする。
2. 2の補数(two's complement)をとる

補数とは

「2の補数」「1の補数」について覚書 - Qiita
このサイトがわかりやすかったです。

  • 足すと桁上がりする数のうち最小の数。 (10進数なら98の補数は2)
  • 足しても桁上がりしない数のうち最大の数。 (10進数なら98の補数は1)

特に2進数は

  • 2の補数(足すと桁上がりする数のうち最小の数。) (ビット反転)
  • 1の補数(足しても桁上がりしない数のうち最大の数。) (ビット反転した数に1を足す)

があります。

128をi8でキャストすると-128になる理由

キャスト (型変換) | サイト構築日記
Rustではないですが、他の言語でも同様の現象があるので、こちらのサイトがわかりやすかったです。

-128になるのをみて、一瞬思ったのが、「128の2の補数は128ではないか」と思いました。
128の2の補数は128で間違い無いのですが、解説を読むとわかりました。

i8の範囲は「-128~127」なので8個のbitを使用しますが、8bit目は符号の役割を果たします。
なので128はi8の範囲からあふれています。
128は2進数では「10000000」となりますが、i8では8個目のビットが立っているとマイナス表現負の数は2の補数で処理されるので-128と表現されてしまいます。
これが、128の2の補数は128ですがi8は-128となってしまう原因です。

感想

補数という言葉を知らなかったので、知れてよかったと思いました。rustだけではなく、他の言語でも同様の現象起こりそうなので、ハマった時に思い出せたら役に立つと思いました。

RustのLinked ListのサンプルでBox<T>の使い方を学習した

apps.apple.com


Linked ListのサンプルでBoxの使い方が出ていて、理解が深まったので備忘録として置いておきます。

コード

テストケース: 連結リスト | Rust by Example
ここより拝借

use List::*;

enum List {
    // Cons: これは、要素をラップし、次の要素へのポインタを保持するタプル。
    Cons(u32, Box<List>),
    // Nil: 連結リストの終端であることを示すノード
    Nil,
}

// 列挙型にはメソッドを付与することができる。
impl List {
    // 空リストの作成。
    fn new() -> List {
        // `Nil` は `List`型を持つ。
        Nil
    }

    // リストを受け取り、その始端に新しい要素を付加したものを返す関数。
    fn prepend(self, elem: u32) -> List {
        // この`Cons` 自体も、その第2要素もどちらもlist型である。
        Cons(elem, Box::new(self))
    }

    // list の長さを返すメソッド
    fn len(&self) -> u32 {
        // このメソッドは、`self`の状態によって振る舞いが
        // 変化するため、matchをする必要がある。
        // `self`の型は`&List`であるので、`*self`は`List`になる。マッチングは
        // リファレンス(`&T`)ではなく実体(`T`)に対して行うのが好ましい。
        match *self {
            // `self`をすでに借用しているので、tailの所有権を取ることができない。
            // 代わりに参照を使用する。
            Cons(_, ref tail) => 1 + tail.len(),
            // 空リストならば長さは0
            Nil => 0
        }
    }

    // Listをheap上の文字列として表したものを返すメソッド。
    fn stringify(&self) -> String {
        match *self {
            Cons(head, ref tail) => {
                // `format!`は`print!`に似ているが、コンソール上に出力
                // する代わりに、heap上の文字列を返す。
                format!("{}, {}", head, tail.stringify())
            },
            Nil => {
                format!("Nil")
            },
        }
    }
}

fn main() {
    // 空の連結リストを作成
    let mut list = List::new();

    // 要素を追加
    list = list.prepend(1);
    list = list.prepend(2);
    list = list.prepend(3);

    // 追加後の状態を表示
    println!("linked list has length: {}", list.len());
    println!("{}", list.stringify());
}

Box

引用

ボックスは、間接参照とヒープメモリ確保だけを提供します。他のスマートポインタ型で目撃するような、 他の特別な能力は何もありません。これらの特別な能力が招くパフォーマンスのオーバーヘッドもないので、 間接参照だけが必要になる唯一の機能であるコンスリストのような場合に有用になり得ます。 より多くのボックスのユースケースは第17章でもお見かけするでしょう。

ユースケースをたくさん見た方が良い気がしてきましたが、ここでは再帰ユースケースを見ました。
Rustの思想ゆえに、こんなシンプルな機能になっているんだろうなと思いました。

なぜBoxを使用するのか?

Box<T>はヒープのデータを指し、既知のサイズである - The Rust Programming Language
解説はここにあって、まとめると

  • コンパイラは事前に使用される領域のサイズを知る必要がある
  • Linked Listは再帰的なデータ構造型になるのでRustはコンパイル時にサイズを知ることはできない
  • 代わりに、Boxというポインタ型にしてしまうことで事前にサイズを計算することを可能にする

なるほどと思ってしまいました。確かにもしデータ構造を以下のように定義すると

enum List {
    Cons(i32, List),
    Nil,
}

無限ループでサイズを計算しないといけないことになってしまうので事前に計算できない。

感想

再帰の使用例しかまだ見ていないので他にも使いどころがきっとあるんだろうと思いながら、Rust勉強しなかったら、こんなこと考えもしなかっただろうなと思いました!
先はまだまだ長い!

本筋とは関係なけど気になったコード

  • len
  • stringify

共に再帰になっています。(再帰の説明はなしです。)

Box::len()メソッドがあり、これを呼び出すにはT::len()を実装してあげないといけないみたいです。なので実態はBox::len()が呼ばれるので、List::len()が呼ばれる仕組みです。
もし実装していないと以下のようなエラーが出ます。

the method `len` exists but the following trait bounds were not satisfied:

個人的メモ

  • refと&の違いがわかっていない。
  • &と*をなぜ使い分けたかわかっていない。

VSCodeでRustインストールしたのに「Rustup not available」が出るとき (備忘録)

Rustインストールしたのになぜか、「Rustup not available」が出てきて、左下のところでrustを解析中みたいな感じで、くるくるローディングし続けていたので、検索したら以下の記事がヒットしました。
qiita.com

手順は

  1. Rustインストール
  2. Pathを通す
  3. VSCodeの設定

が必要みたいで、3つ目のVSCodeの設定をしていなかったみたいです。

VSCodeの設定

f:id:TAKOYAKING:20200105154726p:plain
上の写真のように
VSCodeの設定を開いて (Cmd + ,)、rustで検索して
Rust Config > Rustup Path にPathの設定をします。


これで解決したのでよかったのですが、以前の職場でRustの環境を作った時はこんなことを設定していなかったので、職場のRust環境はなぜうまくいったのか少し疑問が残ります。

とりあえず、できたので、気にしないことにします。

Unity: AddComponentしたらStartとFixedUpdateで呼び出し順でハマった (備忘録)

apps.apple.com

AutoPuppetを作っているときにAddComponentをしている部分があるのですが、AddComponentをしたScriptとAddComponentで加えたScriptのライフサイクルを少し勘違いしていて、ハマったので備忘録がてらにまとめておきます。

現象

public class Main : MonoBehaviour {
    void Awake () {
        gameObject.AddComponent<Foo>();
    }

    void FixedUpdate () {
        // 何かの処理
    }

    void Update () {
        // 何かの処理
    }

}

public class Foo : MonoBehaviour {
    void Awake () {
        // 何かの処理
    }

    void Start () {
        // 何かの処理
    }
}

勘違いしていたところ

  1. Main.Awake()
  2. Sub.Awake()
  3. Foo.Start()
  4. Main.FixedUpdate()
  5. Main.Update()

だと思っていたのですが、実際は

  1. Main.Awake()
  2. Sub.Awake()
  3. Main.FixedUpdate()
  4. Foo.Start()
  5. Main.Update()

FxiedUpdateの方が他のScriptのStartよりも早く呼ばれることに気づいていませんでした。これのせいでバグが発生してしまいました。

ライフサイクルまだ理解しきれていなかった (涙)

対処法

Foo.Start()をFoo.Awake()にする

これでFixedUpdateより前にFooで準備するメソッドを実行できます。

Foo.Start()を廃止してInitializeメソッドみたいなのを作って、それを実行する

MainでAddComponentした直後にFoo.Initialize()を実行する

MainのFixedUpdateはStartから1フレーム後に実行されるようにする

苦肉の策ですね 笑

感想

そもそもAddComponentをしていた理由はパペットごとに付けるScriptが多いので、手動でつけるは面倒だったので、ソースコードに直接AddComponentを記述する自動化処理をしていましたが、それがちょっとあだになってしまいました!

AddComponentは便利ですが、自分の知識不足があだになってしまいましたので備忘録として残しておきます!

チュートリアルはやっぱり大切だった!(個人開発)

apps.apple.com

UnityAnalitycsで一番最初のステージで離脱してしまっている人の割合を調べると約35% ~ 40%くらいいました。
言い換えると一番最初のステージをクリアした人は60%から65%程度になってしまっていることが発覚しました。

ただ最初のステージをクリアするとその後の離脱率はとても低かったので、もしかして、最初のステージをクリアできるように誘導してあげれば、良い感じになるのではと思い、簡易的なチュートリアルを入れて、効果を測定してみました。

チュートリアルのデザイン

こんな感じにしてみました。
f:id:TAKOYAKING:20191226230333p:plain
チュートリアルと言うよりも簡易的なヒント形式にしています。
開始時にポップアップでメッセージが出てきて、その後はヘルプ画面に格納されるアニメーションがあって、ヘルプ画面からいつでもメッセージを見れるようにしています。

これなら最初にこのメッセージを読むことを強制できますし、ヘルプ画面に格納されるアニメーションがあるので、ヘルプ画面もついでにみてくれるかもしれません。 (・・・と言う想像です。)

効果

UnityAnalitycs管理画面
ビフォー
f:id:TAKOYAKING:20191226222926p:plain
66%

アフター
f:id:TAKOYAKING:20191226222932p:plain
81%


66% -> 81%と露骨に差が出ました!

この効果は一時的なものではなくずっとこんな調子です。

感想

今まではチュートリアルを作ることに少し懐疑的でした。
理由は3つです。

  • 作成が面倒 (実装コスト)
  • UIが変わるとチュートリアルを修正しないといけなくなるかも (実装コスト)
  • 効果あるのかどうかわからず、個人的にはだらだらチュートリアル続くのはユーザー体験を損ねるかもと思ってしまう (ユーザー体験)

特に3つ目はだらだらチュートリアルが続いたり、チュートリアルがうっとしくなったりするのが嫌だったので今まで、あまり説明を入れていませんでした。

ただここまで露骨に効果が出ると、ゲームの開発でチュートリアルへの意識が変わりそうです。

やってよかった!

unityのProjectフォルダタブを複数配置する

apps.apple.com
最近まで知らなかったのですが、projectフォルダを複数表示できるらしいことを知りました。

今まではprojectフォルダが一個しかなかったのでScriptのフォルダとTextureのフォルダを交互に使う場合はいちいち対象のフォルダーまでクリックし直さないといけなかったのでとても面倒でした。

f:id:TAKOYAKING:20191221171908p:plain
こんな感じでProjectタブを複数配置できると一方はScriptを入れているフォルダにして、もう一方はTextureを入れているフォルダを表示すれば、交互に素早くアクセスできます。
結構便利です!

やり方

f:id:TAKOYAKING:20191221172112p:plain
やり方は上の写真のように
Projectタブで右クリック > Add Tab > Project
で追加できます。

unity2018からunity2019にあげるとAnima2d関連でエラーがでた

apps.apple.com
Auto Puppetのプロジェクトをunityを2018から2019.2へバージョンアップしようとしたらanima2dで
AmbiguousMatchException: Ambiguous match found.
というエラーがconsoleに出続ける現象に遭遇しました。
ただ普通に起動はできるみたいですが、気持ち悪いので直しました。

AmbiguousMatchException: Ambiguous match found.
System.RuntimeType.GetMethodImpl (System.String name, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Reflection.CallingConventions callConv, System.Type[] types, System.Reflection.ParameterModifier[] modifiers) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.Type.GetMethod (System.String name, System.Reflection.BindingFlags bindingAttr) (at <437ba245d8404784b9fbab9b439ac908>:0)
Anima2D.AnimationWindowImpl_51_52_53.InitializeReflection () (at Assets/Anima2D/Scripts/Editor/AnimationWindowExtra/AnimationWindowImpl_51_52_53.cs:58)
Anima2D.AnimationWindowImpl_54.InitializeReflection () (at Assets/Anima2D/Scripts/Editor/AnimationWindowExtra/AnimationWindowImpl_54.cs:15)
Anima2D.AnimationWindowImpl_55.InitializeReflection () (at Assets/Anima2D/Scripts/Editor/AnimationWindowExtra/AnimationWindowImpl_55.cs:18)
Anima2D.AnimationWindowImpl_56.InitializeReflection () (at Assets/Anima2D/Scripts/Editor/AnimationWindowExtra/AnimationWindowImpl_56.cs:18)
Anima2D.AnimationWindowImpl_2017_1.InitializeReflection () (at Assets/Anima2D/Scripts/Editor/AnimationWindowExtra/AnimationWindowImpl_2017_1.cs:17)
Anima2D.AnimationWindowExtra..cctor () (at Assets/Anima2D/Scripts/Editor/AnimationWindowExtra/AnimationWindowExtra.cs:57)
Rethrow as TypeInitializationException: The type initializer for 'Anima2D.AnimationWindowExtra' threw an exception.
Anima2D.EditorUpdater.AnimationWindowTimeCheck () (at Assets/Anima2D/Scripts/Editor/EditorUpdater.cs:108)
Anima2D.EditorUpdater.Update () (at Assets/Anima2D/Scripts/Editor/EditorUpdater.cs:185)
System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <437ba245d8404784b9fbab9b439ac908>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.Delegate.DynamicInvokeImpl (System.Object[] args) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.MulticastDelegate.DynamicInvokeImpl (System.Object[] args) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.Delegate.DynamicInvoke (System.Object[] args) (at <437ba245d8404784b9fbab9b439ac908>:0)
UnityEditor.EditorApplication.Internal_CallUpdateFunctions () (at /Users/builduser/buildslave/unity/build/Editor/Mono/EditorApplication.cs:310)
環境
  • 2018.2.2f1 -> 2019.2.16f1 のアップデート
  • Mac
対処法
  1. unityをアップデート
  2. エラーが出るのでいったんanima2dフォルダをまるごと削除してAsset Storeからanima2dを再度インポート