游戏蛮牛学习群(纯技术交流,不闲聊):539178957
游戏蛮牛 手机端
开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

教程分享

关注:779

当前位置:游戏蛮牛 技术专区 教程分享

查看: 707|回复: 0

[实例教程] UniRx - Unity响应式编程插件(3):让协程更有效率

[复制链接]  [移动端链接]

31

主题

33

帖子

92

积分

Rank: 2Rank: 2

UID
24181
好友
0
蛮牛币
299
威望
0
注册时间
2014-5-7
在线时间
38 小时
最后登录
2018-8-7
发表于 2018-2-5 20:31:45 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
本文首发于“洪流学堂”微信公众号。
洪流学堂,让你快人几步成为Unity大牛!
本文译者:郑洪智 - 你的技术探路者
翻译日期2018年2月1日
本文翻译自UniRx(https://github.com/neuecc/UniRx)插件的ReadMe
这个插件是我特别喜欢的一个插件,希望能将这种技术思想传播给大家
UniRx是什么?
UniRx (Unity响应式编程插件) 重写了.Net的响应式扩展。.Net官方的Rx很棒,但是在Unity中无法使用,并且与IOS的IL2CPP有兼容性问题。这个库这些问题并且添加了一些Unity专属的工具类。 支持的平台有:PC/Mac//iOS/WP8/WindowsStore/等等,并且支持Unity4.6之后的所有版本。
前情回顾
在UniRx - Unity响应式编程插件(1)[http://mp.weixin.qq.com/s/jvSYTplCYHeAL1XrATOeJQ]中讲了
  • 如何让你的代码更简洁
  • 如何简化网络操作
  • 如何和协程一起使用
在UniRx - Unity响应式编程插件(2)[http://mp.weixin.qq.com/s/fzQ3fU5CwE5DZayWbclIcw]中讲了
  • 让多线程更简单
  • 使用触发器让代码更简洁
  • Observable 生命周期管理
将Unity回调转为IObservable
使用Subject (或AsyncSubject用于异步操作):
public class LogCallback
{
    public string Condition;
    public string StackTrace;
    public UnityEngine.LogType LogType;
}

public static class LogHelper
{
    static Subject<LogCallback> subject;

    public static IObservable<LogCallback> LogCallbackAsObservable()
   
{
        if (subject == null)
        {
            subject = new Subject<LogCallback>();

            // Publish to Subject in callback
            UnityEngine.Application.RegisterLogCallback((condition, stackTrace, type) =>
            {
                subject.OnNext(new LogCallback { Condition = condition, StackTrace = stackTrace, LogType = type });
            });
        }

        return subject.AsObservable();
    }
}

// method is separatable and composable
LogHelper.LogCallbackAsObservable()
    .Where(x => x.LogType == LogType.Warning)
    .Subscribe();

LogHelper.LogCallbackAsObservable()
    .Where(x => x.LogType == LogType.Error)
    .Subscribe();
中移除了 Application.RegisterLogCallback,并用 Application.logMessageReceived替代。所以可以更简单地用 Observable.FromEvent。
public static IObservable<LogCallback> LogCallbackAsObservable()
{
    return Observable.FromEvent<Application.LogCallback, LogCallback>(
        h => (condition, stackTrace, type) => h(new LogCallback { Condition = condition, StackTrace = stackTrace, LogType = type }),
        h => Application.logMessageReceived += h, h => Application.logMessageReceived -= h);
}
流式日志记录器Stream Logger// using UniRx.Diagnostics;

// logger is threadsafe, define per class with name.
static readonly Logger logger = new Logger("Sample11");

// call once at applicationinit
public static void ApplicationInitialize()
{
    // Log as Stream, UniRx.Diagnostics.ObservableLogger.Listener is IObservable<LogEntry>
    // You can subscribe and output to any place.
    ObservableLogger.Listener.LogToUnityDebug();

    // for example, filter only Exception and upload to web.
    // (make custom sink(IObserver<EventEntry>) is better to use)
    ObservableLogger.Listener
        .Where(x => x.LogType == LogType.Exception)
        .Subscribe(x =>
        {
            // ObservableWWW.Post("", null).Subscribe();
        });
}

// Debug is write only DebugBuild.
logger.Debug("Debug Message");

// or other logging methods
logger.Log("Message");
logger.Exception(new Exception("test exception"));
调试Debugging
UniRx.Diagnostics 中的 Debug 操作符可以帮助调试。
// needs Diagnostics using
using UniRx.Diagnostics;

---

// [DebugDump, Normal]OnSubscribe
// [DebugDump, Normal]OnNext(1)
// [DebugDump, Normal]OnNext(10)
// [DebugDump, Normal]OnCompleted()
{
    var subject = new Subject<int>();

    subject.Debug("DebugDump, Normal").Subscribe();

    subject.OnNext(1);
    subject.OnNext(10);
    subject.OnCompleted();
}

// [DebugDump, Cancel]OnSubscribe
// [DebugDump, Cancel]OnNext(1)
// [DebugDump, Cancel]OnCancel
{
    var subject = new Subject<int>();

    var d = subject.Debug("DebugDump, Cancel").Subscribe();

    subject.OnNext(1);
    d.Dispose();
}

// [DebugDump, Error]OnSubscribe
// [DebugDump, Error]OnNext(1)
// [DebugDump, Error]OnError(System.Exception)
{
    var subject = new Subject<int>();

    subject.Debug("DebugDump, Error").Subscribe();

    subject.OnNext(1);
    subject.OnError(new Exception());
}
将按事件顺序显示 OnNext, OnError, OnCompleted, OnCancel, OnSubscribe 的调用并通过Debug.Log打印出来。只在 #if DEBUG时生效。
Unity-specific Extra Gems(针对Unity的超酷额外功能)
译者注:SubscribeOnMainThread()经测试现在没有效果,有BUG(2018年1月30日,UniRx版本5.5)
// Unity's singleton UiThread Queue Scheduler
Scheduler.MainThreadScheduler
ObserveOnMainThread()/SubscribeOnMainThread()

// Global StartCoroutine runner
MainThreadDispatcher.StartCoroutine(enumerator)

// convert Coroutine to IObservable
Observable.FromCoroutine((observer, token)
=> enumerator(observer, token));

// convert IObservable to Coroutine
yield return Observable.Range(1, 10).ToYieldInstruction(); // after Unity 5.3, before can use StartAsCoroutine()

// Lifetime hooks
Observable.EveryApplicationPause();
Observable.EveryApplicationFocus();
Observable.OnceApplicationQuit();
Framecount-based time operators(基于帧数的时间操作)
UniRx 提供了一些基于帧数的时间操作:
Method

EveryUpdate
EveryFixedUpdate
EveryEndOfFrame
EveryGameObjectUpdate
EveryLateUpdate
ObserveOnMainThread
NextFrame
IntervalFrame
TimerFrame
DelayFrame
SampleFrame
ThrottleFrame
ThrottleFirstFrame
TimeoutFrame
DelayFrameSubscription
FrameInterval
FrameTimeInterval
BatchFrame
例如,一次延迟调用:
Observable.TimerFrame(100).Subscribe(_ => Debug.Log("after 100 frame"));
Every* 方法们的执行顺序是
EveryGameObjectUpdate(in MainThreadDispatcher's Execution Order) ->
EveryUpdate ->
EveryLateUpdate ->
EveryEndOfFrame
EveryGameObjectUpdate 在同一帧被调用,从 MainThreadDispatcher.Update 中调用(作者建议 MainThreadDispatcher 脚本比其他脚本先执行(ScriptExecutionOrder 设置为 -32000)
EveryLateUpdate, EveryEndOfFrame 在同一帧调用。
然后在下一帧调用EveryGameObjectUpdate,以此类推
MicroCoroutine(微协程)
MicroCoroutine 在内存上更有效率,而且更快。这是基于Unity的这篇博客《10000 UPDATE() CALLS》(http://blogs.unity3d.com/2015/12/23/1k-update-calls/)实现的,可以快上几十倍。MicroCoroutine会自动用于基于帧数的时间操作和ObserveEveryValueChanged。
如果你想要使用MicroCoroutine替代unity内置的协程,使用 MainThreadDispatcher.StartUpdateMicroCoroutine 或者 Observable.FromMicroCoroutine。
int counter;

IEnumerator Worker()
{
    while(true)
    {
        counter++;
        yield return null;
    }
}

void Start()
{
    for(var i = 0; i < 10000; i++)
    {
        // fast, memory efficient
        MainThreadDispatcher.StartUpdateMicroCoroutine(Worker());

        // slow...
        // StartCoroutine(Worker());
    }
}
image
MicroCoroutine的局限是:仅支持 yield return null 并且调用的时间是根据调用的方法来确定(StartUpdateMicroCoroutine, StartFixedUpdateMicroCoroutine, StartEndOfFrameMicroCoroutine).。
如果和其他IObservable一起用,你可以检查完成属性比如isDone。
IEnumerator MicroCoroutineWithToYieldInstruction()
{
    var www = ObservableWWW.Get("http://aaa").ToYieldInstruction();
    while (!www.IsDone)
    {
        yield return null;
    }

    if (www.HasResult)
    {
        UnityEngine.Debug.Log(www.Result);
    }
}
未完结,请关注“洪流学堂”公众号后续连载
关注“洪流学堂”微信公众号,让你快人几步成为Unity大牛

[发帖际遇]: 关尔Manic 乐于助人,奖励 3 蛮牛币. 幸运榜 / 衰神榜

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表