てくメモ

trivial な notes

【Godot C#】R3.Godot のUI用便利拡張メソッド

現在プレビューリリースされている Rx ライブラリR3。Unity の R3.Unity にはUI用便利拡張メソッド群の UnityUIComponentExtensions が用意されているが、Godot にはなかった。

ver.0.1.6 までは。

PRを出して無事マージしてもらい、Godot でも同じような感じのものが利用できるようになった(ver. 0.1.7~)。動作確認をとったものがショーケースに流用できると思ったので、以下に動画とコードを貼って記事に。

https://i.imgur.com/C76OmhE.png (apng なので表示・再生できないことがあるかもしれない)

public partial class UiShowcase : Node2D
{
    private readonly CancellationTokenSource cts = new();

    public override void _Ready()
    {
        var button = GetNode<Button>("Button");
        var label = GetNode<Label>("Label");
        var toggleButton = GetNode<Button>("ToggleButton");
        var label2 = GetNode<Label>("Label2");
        var checkButton = GetNode<CheckButton>("CheckButton");
        var label3 = GetNode<Label>("Label3");
        var slider = GetNode<HSlider>("HSlider");
        var label4 = GetNode<Label>("Label4");
        var spinBox = GetNode<SpinBox>("SpinBox");
        var label5 = GetNode<Label>("Label5");
        var lineEdit = GetNode<LineEdit>("LineEdit");
        var label6 = GetNode<Label>("Label6");
        var lineEdit2 = GetNode<LineEdit>("LineEdit2");
        var label7 = GetNode<Label>("Label7");
        var textEdit = GetNode<TextEdit>("TextEdit");
        var label8 = GetNode<Label>("Label8");
        var optionButton = GetNode<OptionButton>("OptionButton");
        var label9 = GetNode<Label>("Label9");

        var countSubscriptionsButton = GetNode<Button>("CountSubscriptionsButton");
        var labelSubscriptionCount = GetNode<Label>("LabelSubscriptionCount");
        var cancelAndGcButton = GetNode<Button>("CancelAndGcButton");
        var labelCanceled = GetNode<Label>("LabelCanceled");

        SubscriptionTracker.EnableTracking = true;
        //SubscriptionTracker.EnableStackTrace = true;

        // OnPressedAsObservable
        var count = 0;
        button.OnPressedAsObservable(cts.Token)
            .Select(_ => ++count)
            .SubscribeToLabel(label);

        // OnToggledAsObservable
        toggleButton.OnToggledAsObservable(cts.Token)
            .SubscribeToLabel(label2);
        checkButton.OnToggledAsObservable(cts.Token)
            .SubscribeToLabel(label3);

        // OnValueChangedAsObservable
        slider.OnValueChangedAsObservable(cts.Token)
            .SubscribeToLabel(label4);
        spinBox.OnValueChangedAsObservable(cts.Token)
            .SubscribeToLabel(label5);

        // OnTextSubmittedAsObservable
        lineEdit.OnTextSubmittedAsObservable(cts.Token)
            .SubscribeToLabel(label6);

        // OnTextChangedAsObservable
        lineEdit2.OnTextChangedAsObservable(cts.Token)
            .SubscribeToLabel(label7);
        textEdit.OnTextChangedAsObservable(cts.Token)
            .Select(_ => textEdit.Text)
            .SubscribeToLabel(label8);

        // OnItemSelectedAsObservable
        optionButton.OnItemSelectedAsObservable(cts.Token)
            .SubscribeToLabel(label9);

        // for debug
        countSubscriptionsButton.Pressed += () =>
        {
            var count = 0;
            SubscriptionTracker.ForEachActiveTask(state =>
            {
                ++count;
                if (SubscriptionTracker.EnableStackTrace) GD.Print(state);
            });

            labelSubscriptionCount.Text = count.ToString();
        };
        cancelAndGcButton.Pressed += () =>
        {
            GD.Print("cancel & GC");
            cts.Cancel();
            GC.Collect();

            labelCanceled.Text = "CANCELED!";
        };
    }

    protected override void Dispose(bool disposing)
    {
        cts.Cancel();
        cts.Dispose();
    }
}

比較的頻出のUIイベントをシンプルに利用できる。イベント処理をベタ書きしようとするとObservable.FromEvent利用となるが、少しカロリーの高い書き方になるので、手軽に Rx するために便利拡張メソッドがあるのは良い。

キャンセルトークンについて

R3.Unity との違いとして、引数にキャンセルトークンを受ける(省略可能)。

これはどちらかというとなぜ R3.Unity がそうなっていないのか、という話。Unity はdestroyCancellationTokenという仕組みを持ち、R3.Unity は暗黙にそのトークンに利用している。

一方、明示的にトークンを渡せるのは任意タイミングのキャンセル可といったプラスの面もある。省略可能引数なので、選択肢が増えている、という捉え方もできる。


ちなみに、ver.0.1.7 は購読トラッカーのエディタUI実装 & R3.Godot のプラグイン化という大きな前進もあった。

R3、Godot に馴染んでくれて、良い。