【Unity】R3与UniRx的对比总结

本文前提

	UniRx 是已经19年就停更的产物,原作者选择在UniRx的基础上,推出适应更多平台和性能更好的R3。
	二者不可兼容升级,由于国内没有对应R3的教程,所以本文为机翻搬运。原作者:@toRisouP,链接:https://qiita.com/toRisouP/items/4344fbcba7b7e8d8ce16

前提


「R3」是根据ReactiveExtensions最新环境的c#重新构建的库。Unity中有一个名为“UniRx”的库,粗略地说,可以认为是“将UniRx按照最新的环境进行重制”。

详细内容会在别的报道中总结。

另外,本文执笔时的环境如下所示。

  • Unity - 2023.1.14f1
  • R3 - 1.0.0
  • ObservableCollections - 2.0.1
  • NuGetForUnity - 4.0.2

这次的概要


介绍“UniRx”和“R3”的功能比较,R3的新功能和被废除的功能,从UniRx换成R3时的替代等。(细节部分不能全部挑出来,介绍遗漏请见谅。另外,我们省略了非面向Unity的功能。)

在这篇文章中出现的样本代码是CC0,除非另有记载。请自由复制粘贴使用。
(但是发生的纠纷和问题不负责)

另外,我们在GitHub上发布了样本项目。

操作环境等的差异


最低Unity版本


UniRx
UniRx没有特别的最低版本,Unity 2017的相当旧的Unity版本也能运行。

R3
R3至少需要Unity 2021.3以上。

补充:destroyCancellationToken
R3是通过CancellationToken来管理取消的机制。

Unity 2022.2以后可以在MonoBehaviour上使用destroyCancellationToken,使用这个很方便。

在Unity 2022.2以下的情况下,使用R3提供的ObservableDestroyTrigger,可以得到相同的功能。
(Unity2022.2以后使用ObservableDestroyTrigger也没有问题)

using System;
using Cysharp.Threading.Tasks;
using R3;
using UnityEngine;

public class DestroySample : MonoBehaviour
{
    private void Start()
    {
        // Unity 2022.2以后可以使用destroyCancellationToken
        Observable
            .Timer(TimeSpan.FromSeconds(3), destroyCancellationToken)
            .Subscribe();
        
        // 如果不使用destroyCancellationToken,GetCancellationTokenOnDestroy代替使用就OK
        // (Unity2022.2以后的话只是内部返回destroyCancellationToken所以没有成本)
        Observable
            .Timer(TimeSpan.FromSeconds(3), this.GetCancellationTokenOnDestroy())
            .Subscribe();
    }
}

导入方法


UniRx
UniRx必须通过以下方法之一来导入。

  • unitypackage
  • UPM (Git)
  • OpenUPM

R3
R3分为“核心模块”和“Unity插件”两部分,在Unity中运行全部功能时需要安装这两者。在R3的官方文档中记载了两者的安装方法。

因为核心模块需要通过Nuget安装,所以推荐使用NugetForUnity。请通过UnityPackageManager通过Git安装Unity插件(R3.Unity)。

详细内容请参考官方文档。

如果你在asmdef中进行模块管理,请添加R3.Unity作为参考

行为上的根本差异

R3从根本上重新考虑了Observable的概念,因此其行为与UniRx有很大的不同。

  • OnError消息被改为OnErrorResume消息
  • 发布OnCompleted消息时可以选择“正常结束”还是“异常结束(包含异常)”
  • 所有的Observable都可以最后发布OnCompleted消息了
  • Scheduler被废除了
  • async/await的合作变得容易了
  • CancellationToken更易控制

关于这一点,在别的报道中已经解说完毕,请参考下面的报道。

大的改动点写在README里


R3的README中写了差异,所以先读那个吧

从UniRx转移到R3也能直接使用的功能


UniRx中经常使用的功能在R3中也存在。因此,在R3以后的功能中也可以同样使用。

Trigger (MonoBehaviour的事件转换)


将UniRx中存在的MonoBehaviour事件转换为Observable的功能(Trigger),在R3中也可以使用。

using R3;
using R3.Triggers;
using UnityEngine;

namespace Samples.R3Sample
{
    public class TriggerSample : MonoBehaviour
    {
        private void Start()
        {
            // 可以获取与该GameObject相关联的OnCollisionEnter作为Observable
            this.OnCollisionEnterAsObservable()
                .Subscribe(collision =>
                {
                    Debug.Log("OnCollisionEnter: " + collision.gameObject.name);
                });

            // 可以获取Update()作为Observable
            this.UpdateAsObservable()
                .Subscribe(_ =>
                {
                    Debug.Log("Update!");
                });
            
            // 还有很多其他的
        }
    }
}

AddTo(MonoBehaviour)


将IDisposable联动到MonoBehaviour寿命的AddTo(this),也可以用在R3上。

using R3;
using R3.Triggers;
using UnityEngine;

namespace Samples.R3Sample
{
    public class AddToSample : MonoBehaviour
    {
        [SerializeField] private GameObject _childObject;

        private void Start()
        {
            // 获取与childObject相关联的OnCollisionEnter作为Observable           
            _childObject
                .OnCollisionEnterAsObservable()
                .Subscribe(collision =>
                {
                    Debug.Log("OnCollisionEnter: " + collision.gameObject.name);
                })
                // 将Observable的寿命与这个MonoBehaviour挂钩
                .AddTo(this);
        }
    }
}

uGUI组件的事件转换


UnityEngine.UI.Button等所谓的“uGUI”事件转换为Observable的功能可以继续从UniRx使用。

可以使用哪些活动请参照这里

using R3;
using UnityEngine;
using UnityEngine.UI;

namespace Samples.R3Sample
{
    public class GuiEventSample : MonoBehaviour
    {
        [SerializeField] private Button _button;
        [SerializeField] private InputField _inputField;
        [SerializeField] private Slider _slider;
        [SerializeField] private Text _text;

        private void Start()
        {
            // 按钮的点击
            _button
                .OnClickAsObservable()
                .Subscribe(_ => Debug.Log("Button Clicked!"))
                .AddTo(this);

            // InputField的文本变更
            _inputField.OnValueChangedAsObservable()
                .Subscribe(txt => Debug.Log("InputField Text: " + txt))
                .AddTo(this);

            // Slider的值改变
            _slider.OnValueChangedAsObservable()
                .Subscribe(v => Debug.Log("Slider Value: " + v))
                .AddTo(this);

            // 在Text中反映InputField的文本
            _inputField.OnValueChangedAsObservable()
                .SubscribeToText(_text)
                .AddTo(this);
        }
    }
}

R3的新功能(UniRx没有的功能)


[新功能] SubscribeAwait/SelectAwait/WhereAwait


在新一代Rx [R3]的解说中也有说明,追加了SubscribeSelect/Where可以同时使用async/await的版本。在R3的情况下,async/await的完成和消息处理控制的感觉很好。(与UniTask组合更方便!)

using Cysharp.Threading.Tasks;
using R3;
using UnityEngine;
using UnityEngine.UI;

namespace Samples.R3Sample
{
    public class SubscribeAwaitSample1 : MonoBehaviour
    {
        [SerializeField] private Button _goButton;

        private void Start()
        {
            // 按下按钮后前进一秒钟
            // 被连击的情况下,按此数前进
            _goButton.OnClickAsObservable()
                .SubscribeAwait(async (_, ct) =>
                {
                    var time = Time.time;
                    while (Time.time - time < 1f)
                    {
                        transform.position += Vector3.forward * Time.deltaTime;
                        await UniTask.Yield(ct);
                    }
                }, 
                    AwaitOperation.Sequential,
                    // configureAwait建议从true开始不变
                    configureAwait: true)
                .AddTo(this);
        }
    }
}

在这里插入图片描述
注意,configureAwait指定True。(默认为真)
如果指定为假,执行上下文可能会被无意中切换到线程池。

另外,通过指定AwaitOperation这个参数,可以在异步处理执行中(await处理结束之前)调整下一条消息到达时的行为。

AwaitOpenrationawait 下一个事件发生时的举动备考
Sequential优先现在执行中的处理。把剩余的活动堆在队列里。异步处理结束后,取出下一个按顺序异步执行。
Drop优先现在执行中的处理。剩余的活动就当作没有忽视。
Switch取消现在执行中的异步处理。优先开始处理新到达的事件。取消处理需要使用CancellationToken自己实现。
Parallel立即处理新来的事件。处理结束后,以最快的速度输出。maxConcurrent可以限制同时运行的数量。超过maxConcurrent的数量的消息被堆积在队列中。
SequentialParallel※立即处理新来的事件。不管处理的结束顺序如何,输出顺序被调整为与输入顺序相同。maxConcurrent可以限制同时运行的数量。超过maxConcurrent的数量的消息被堆积在队列中。
ThrottleFirstLast当不执行异步处理时,处理新到达的值。在执行异步处理期间,仅保存一个最新值,并在异步处理结束时取出该最新值进行处理。ThrottleFirstThrottleLast结合的行为

SequentialParallel只能在WhereAwait/SelectAwait中使用

(补充)UniRx是怎么做的

顺便说一下,在UniRx使用async/await的时候,全部都是用async void来操作的。因此,这并不是“与异步处理相协调的动作”。
在这里插入图片描述

[新功能]Debounce / ThrottleFirst / ThrottleLast异步的对应

Debounce (旧名 Throttle) / ThrottleFirst / ThrottleLast (旧名 Sample)是 UniRx也存在的操作人员, R3是异步处理应对了。也就是说可以和async/await一起使用。

各个异步版的行为如下所示。

  • Debounce:消息到达后执行异步处理。异步处理完成后发布那个消息。如果在异步处理中来了下一个消息,则在执行中取消异步处理,重新执行异步处理。
  • ThrottleFirst:消息到达后让它通过,然后执行异步处理,直到处理结束为止切断消息。
  • ThrottleLast:当消息到达时,执行异步处理来阻断消息,当该处理结束时,只发布一个最后到达的消息。

DebounceThrottleLast的区别在于异步处理是重新进行还是完成。Debounce每次来消息都要重做异步处理。ThrottleLast是一旦开始跑,就一直跑到跑完为止。


未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值