Unity实现自己的协程系统

news2024/9/17 4:23:14

概述:自定义Unity协程调度器(不依赖Mono)
 

        实现了一个协程调度器,允许在程序中以非阻塞的方式调度协程。协程可以在满足特定条件后暂停和恢复,如等待特定的帧数、时间、或等待其他协程执行完毕。它的设计思想与Unity的协程机制类似,但它不依赖Unity的YieldInstruction,因此适用于非Unity环境。


协程可以在以下情况下暂停:
"yield null ;" 等一帧;
"yield 一个int值",等待给定的帧数;
"yield 一个float值;",等待给定的秒数;
"yield scheduler.StartCoroutine(Coroutine())",直到另一个协程完成。


支持多个调度器实例.一个调度器中的协程可以等待另一个完全不同的调度器实例中的协程。

不使用Unity的YieldInstruction类,因为无法访问其内部数据进行调度。调度语义与Unity的调度器略有不同。
         例如,在Unity中,如果启动协程,它会立即运行到第一个yield,而在这个调度器中,它直到下一次调用UpdateAllCoroutines才会运行。
这个特性允许在任何时候启动协程,但确保启动的协程只在特定时间运行。

在同一个更新中运行的协程之间不要依赖更新顺序。例如,StartCoroutine(A), StartCoroutine(B), StartCoroutine(C)
        如果A、B、C都为:while(true) { print(A|B|C); yield; },不要期望 "ABC" 或 "CBA" 或任何特定的顺序。


 代码结构:
CoroutineScheduler 类:

 这个类是协程调度器的核心,它可以管理多个协程并根据指定的调度规则执行协程。
StartCoroutine: 启动一个新的协程。协程不会立即执行,而是在下一次调用UpdateAllCoroutines时开始运行。它可以接受IEnumerator或IEnumerable作为输入。

 Rewind: 允许重置并重新运行一个已完成的协程。

 Stop/StopAllCoroutines: 停止单个或所有协程。StopAllCoroutines停止调度器中的所有协程.

 Pause/Resume: 可以暂停和恢复协程的执行。恢复时协程会从暂停时的位置继续。

 UpdateAllCoroutines: 执行调度器中的所有协程,直到它们的下一个yield。需要调用者提供当前帧数和时间来控制协程的进度。这个方法是调度器的核心,它根据不同的条件(如时间、帧数等)来判断协程是否需要继续执行。

 CoroutineNode 类:

 每个协程都被封装在一个CoroutineNode对象中,保存了协程的执行状态、等待的条件(如等待的帧数或时间)以及是否暂停、完成等状态。

 waitForFrame/waitForTime: 协程的等待条件,当协程需要等待一定的帧数或时间时会使用这些字段。

 Reset: 允许重置协程,使其可以从头开始运行。

 Pause/Resume: 这些方法用来控制协程的暂停和恢复。

 IYieldWrapper 接口:

 这个接口允许实现一些自定义的等待条件,比如等待某个Unity对象执行完特定任务后才继续协程。

 工作原理:
 每次UpdateAllCoroutines被调用时,调度器会遍历所有未完成的协程,并根据协程当前的等待条件(帧数、时间或其他协程)决定是否继续执行。如果协程满足其等待条件,就继续执行它直到遇到下一个yield。

 在协程执行的过程中,可以通过yield return指定暂停条件。当满足条件时,调度器会将协程从等待状态中恢复。


这个调度器的设计受Unity协程机制的启发,但它并不依赖于Unity引擎,因此可以在任何基于帧更新或时间驱动的系统中使用。

 这个调度器的灵活性允许你在任意的时间调度和控制协程,并且可以在多个调度器实例之间进行协作。

using System.Collections;
public class CoroutineScheduler
{
    public CoroutineNode first = null; // 链表的第一个协程节点
    int currentFrame; // 当前帧数
    float currentTime; // 当前时间
    CoroutineNode runTimeNuext; // 执行时的下一个节点

    /**
    * 启动一个协程,协程不会立即运行,而是在下一次调用UpdateAllCoroutines时运行。
    * 协程的执行可以在任何时候使用yield语句暂停。yield返回值指定协程何时恢复。
    */
    public CoroutineNode StartCoroutine(IEnumerator fiber)
    {
        // 如果函数没有yield,fiber将为null,什么也不做
        if (fiber == null)
        {
            return null;
        }

        // 创建协程节点并运行,直到到达第一个yield
        CoroutineNode coroutine = new CoroutineNode(fiber);
        AddCoroutine(coroutine);
        return coroutine;
    }

    public CoroutineNode StartCoroutine(IEnumerable srcFiber)
    {
        if (srcFiber == null)
        {
            return null;
        }

        CoroutineNode coroutine = new CoroutineNode(srcFiber.GetEnumerator());
        coroutine.srcFiber = srcFiber;
        AddCoroutine(coroutine);
        return coroutine;
    }

    public void Rewind(CoroutineNode cn)
    {
        if (cn.srcFiber == null)
            throw new System.Exception("不是IEnumerable函数");
        if (cn.finished)
        {
            AddCoroutine(cn);
        }

        cn.Reset(); // 重置协程节点
    }

    public void Stop(CoroutineNode cn)
    {
        RemoveCoroutine(cn);
        cn.finished = true; // 标记协程已完成
    }

    public void Pause(CoroutineNode cn)
    {
        if (cn.finished) return;
        if (!cn.isPaused)
        {
            cn.isPrePause = true;
            cn.isPaused = true;
        }
    }

    public void Resume(CoroutineNode cn)
    {
        if (cn.finished) return;
        if (cn.isPaused)
        {
            cn.isPaused = false;
            cn.isPrePause = true;
            AddCoroutine(cn); // 恢复协程
        }
    }

    /**
    * 停止所有在该调度器上运行的协程。建议避免使用此方法,
    * 应找到一种自然的方式让协程自行完成,而不是在它们完成之前被强制停止。
    * 如果你需要更细粒度的控制来停止协程,你可以使用多个调度器。
    */
    public void StopAllCoroutines()
    {
        CoroutineNode cn = first;
        CoroutineNode next;
        while (cn != null)
        {
            next = cn.listNext;
            cn.listPrevious = null;
            cn.listNext = null;
            cn = next;
        }

        first = null; // 清空调度器中的所有协程
    }

    /**
    * 如果此调度器有任何协程运行,则返回true。你可以使用它来检查所有协程是否已完成或被停止。
    */
    public bool HasCoroutines()
    {
        return first != null;
    }

    /**
    * 运行所有活跃的协程直到它们的下一个yield。调用者必须提供当前的帧数和时间。
    * 这允许调度器在不同于Unity主游戏循环的帧数和时间制度下运行。
    */
    public void UpdateAllCoroutines(int frame, float time)
    {
        currentFrame = frame;
        currentTime = time;
        CoroutineNode coroutine = this.first;
        while (coroutine != null)
        {
            // 在协程完成并从列表中移除之前,存储listNext
            runTimeNuext = coroutine.listNext;
            if (coroutine.isPaused) // 如果协程暂停,直接进入下一个节点
            {
                if (coroutine.isPrePause)
                {
                    coroutine.isPrePause = false;
                    if (coroutine.waitForFrame > 0) coroutine.waitForFrame -= currentFrame;
                    else if (coroutine.waitForTime > 0) coroutine.waitForTime -= currentTime;
                    else if (coroutine.waitForCoroutine != null) Pause(coroutine.waitForCoroutine);
                    else if (coroutine.waitForUnityObject != null) coroutine.waitForUnityObject.Pause();
                }

                RemoveCoroutine(coroutine);
                coroutine = runTimeNuext;
                continue;
            }
            else if (coroutine.isPrePause)
            {
                coroutine.isPrePause = false;
                if (coroutine.waitForFrame > 0) coroutine.waitForFrame += currentFrame;
                else if (coroutine.waitForTime > 0) coroutine.waitForTime += currentTime;
                else if (coroutine.waitForCoroutine != null) Resume(coroutine.waitForCoroutine);
                else if (coroutine.waitForUnityObject != null) coroutine.waitForUnityObject.Resume();
            }

            if (coroutine.waitForFrame > 0 && frame >= coroutine.waitForFrame)
            {
                coroutine.waitForFrame = -1;
                InitUpdateCoroutine(coroutine);
            }
            else if (coroutine.waitForTime > 0.0f && time >= coroutine.waitForTime)
            {
                coroutine.waitForTime = -1.0f;
                InitUpdateCoroutine(coroutine);
            }
            else if (coroutine.waitForCoroutine != null && coroutine.waitForCoroutine.finished)
            {
                coroutine.waitForCoroutine = null;
                InitUpdateCoroutine(coroutine);
            }
            else if (coroutine.waitForUnityObject != null && coroutine.waitForUnityObject.finished)
            {
                coroutine.waitForUnityObject = null;
                InitUpdateCoroutine(coroutine);
            }
            else if (coroutine.waitForFrame == -1 && coroutine.waitForTime == -1.0f
                                                  && coroutine.waitForCoroutine == null &&
                                                  coroutine.waitForUnityObject == null)
            {
                // 初始更新
                InitUpdateCoroutine(coroutine);
            }

            coroutine = runTimeNuext;
        }
    }

    /**
    * 执行协程直到下一个yield。如果协程已完成,标记它为完成并将其从调度器列表中移除。
    */
    void InitUpdateCoroutine(CoroutineNode coroutine)
    {
        IEnumerator fiber = coroutine.fiber;
        if (coroutine.fiber.MoveNext())
        {
            // System.Object yieldCommand = fiber.Current == null ? (System.Object)1 : fiber.Current;
            System.Object yieldCommand = fiber.Current;
            if (fiber.Current == null)
            {
                coroutine.waitForFrame = 1 + currentFrame;
            }
            else if (yieldCommand is int)
            {
                coroutine.waitForFrame = (int)yieldCommand + currentFrame;
            }
            else if (yieldCommand is float)
            {
                coroutine.waitForTime = (float)yieldCommand + currentTime;
            }
            else if (yieldCommand is CoroutineNode)
            {
                coroutine.waitForCoroutine = (CoroutineNode)yieldCommand;
            }
            else if (yieldCommand is IYieldWrapper)
            {
                coroutine.waitForUnityObject = yieldCommand as IYieldWrapper;
            }
            else
            {
                throw new System.ArgumentException("CoroutineScheduler: 意外的协程yield类型: " + yieldCommand.GetType());
            }
        }
        else
        {
            // 协程完成
            coroutine.finished = true;
            RemoveCoroutine(coroutine);
        }
    }

    void AddCoroutine(CoroutineNode coroutine)
    {
        if (first == null)
        {
            first = coroutine;
            first.listPrevious = null;
            first.listNext = null;
        }
        else
        {
            var c = first;
            while (c.listNext != null)
                c = c.listNext;

            c.listNext = coroutine;
            coroutine.listNext = null;
            coroutine.listPrevious = c;
        }

        coroutine.finished = false; // 协程未完成
    }

    void RemoveCoroutine(CoroutineNode coroutine)
    {
        if (this.first == coroutine)
        {
            // 移除第一个协程节点
            this.first = coroutine.listNext;
            if (first != null) first.listPrevious = null;
        }
        else
        {
            // 不是列表头
            if (coroutine.listNext != null)
            {
                // 移除中间的节点
                coroutine.listPrevious.listNext = coroutine.listNext;
                coroutine.listNext.listPrevious = coroutine.listPrevious;
            }
            else if (coroutine.listPrevious != null)
            {
                // listNext为空,移除最后一个节点
                coroutine.listPrevious.listNext = null;
            }
        }

        if (coroutine == runTimeNuext)
            runTimeNuext = coroutine.listNext;

        coroutine.listPrevious = null;
        coroutine.listNext = null;
    }
}

public interface IYieldWrapper
{
    bool finished { get; }
    void Pause();
    void Resume();
}


public class CoroutineNode : IYieldWrapper
{
    public CoroutineNode listPrevious = null;
    public CoroutineNode listNext = null;
    public IEnumerable srcFiber;
    public IEnumerator fiber;
    public bool finished = true;
    public int waitForFrame = -1;
    public float waitForTime = -1.0f;
    public CoroutineNode waitForCoroutine;
    public IYieldWrapper waitForUnityObject; //lonewolfwilliams

    public bool isPaused = false;
    public bool isPrePause = false;

    public CoroutineNode(IEnumerator _fiber, bool _finished = false)
    {
        this.fiber = _fiber;
        this.finished = _finished;
    }

    public CoroutineNode(IEnumerable _srcFiber, bool _finished = false)
    {
        srcFiber = _srcFiber;
        this.fiber = _srcFiber.GetEnumerator();
        this.finished = _finished;
    }

    public void Reset()
    {
        if (srcFiber != null)
            fiber = srcFiber.GetEnumerator();
        isPaused = false;
        isPrePause = false;
        waitForFrame = -1;
        waitForTime = -1.0f;
        waitForCoroutine = null;
        waitForUnityObject = null;
    }

    bool IYieldWrapper.finished
    {
        get { return finished; }
    }



    public void Pause()
    {
        if (finished) return;
        if (!isPaused)
        {
            isPrePause = true;
            isPaused = true;
        }
    }

    public void Resume()
    {
        if (finished) return;
        if (isPaused)
        {
            isPaused = false;
            isPrePause = true;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

铁路订票系统小程序的设计

管理员账户功能包括:系统首页,个人中心,管理员管理,车次信息管理,基础数据管理,论坛管理,通知公告管理,用户管理,轮播图信息 微信端账号功能包括:系统首页&a…

【linux学习指南】Linux编译器 gcc和g++使用

文章目录 📝前言🌠 gcc如何完成🌉预处理(进行宏替换) 🌠编译(生成汇编)🌉汇编(生成机器可识别代码) 🌠链接(生成可执行文件或库文件)&…

变压器制造5G智能工厂工业物联数字孪生平台,推进制造业数字化转型

变压器制造5G智能工厂工业物联数字孪生平台,推进制造业数字化转型。作为传统制造业的重要组成部分,变压器制造行业也不例外地踏上了数字化转型的快车道。而变压器制造5G智能工厂物联数字孪生平台的出现,更是为这一进程注入了强大的动力&#…

内卷时代无人机培训机构如何做大做强

在当今社会,随着科技的飞速发展,“内卷”一词频繁被提及,反映了各行业竞争日益激烈的现象。对于无人机培训行业而言,如何在这样的时代背景下脱颖而出,实现做大做强的目标,成为每个培训机构必须深思的问题。…

自学C语言-11

** 第3篇 高级应用 ** 第11章 结构体和共用体 迄今为止,我们在程序中用到的都是基本数据类型。但实际开发中,有时简单的变量类型无法满足程序中各种复杂的数据要求,因此C语言还提供了构造类型。构造类型数据是由基本类型数据按照一定规则组成的。 本章致力于使读者了解结…

【Nginx系列】Nginx中rewrite模块

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

《战锤40K:星际战士2》超越《黑神话》 登Steam热销榜首

《使命召唤:黑色行动6》将登陆 PC Game Pass看来确实影响了销量,因为这次在 Steam 上它的预购并没有占领 Steam 热销榜单之首。这次霸榜的则是即将推出的《战锤40K:星际战士2》。 根据 SteamDB 显示,这部将于9 月 10 日发售的游戏…

LabVIEW中Request Deallocation 功能

此功能会在包含该功能的 VI 运行之后释放未使用的内存。 该功能仅适用于高级性能优化。在某些情况下,释放未使用的内存可以提高性能。然而,过于频繁地释放内存可能导致 LabVIEW 反复重新分配空间,而不是重用已有的内存分配。如果您的 VI 分配…

rocky linux 9部署zabbix6

安装rocky9 阿里巴巴开源镜像站http://mirrors.aliyun.com 1、Rocky 2、初始化 防火墙 systemctl stop firewalld systemctl disable filewalld 或者 systemctl disable firewalld --now 3、selinux vi /etc/selinux/config 配置源sed -e s|^mirrorlist|#mirrorlist|g \-e s|^#…

24程序员转行,首选为什么是它?

今天文章的主人公暂且称他为 A 君。不过 A 君有点特别,非科班,工作 10 年后才转行 iOS 程序员。今年 36 岁,目前在某行业头部企业任职前端负责人,管理 40 人的前端团队。 废话不多说,我们开始 A 君(为了描…

包机制,javadoc生成文档,用户交互scanner

包机制 在建包时com.kuang直接建线性一条龙的文件只会显示一个外层包,当再建一个包时才会显示出两个包。 import com.kuang.base 导入包的时候在后面加入星号就能把包全导进来 javadoc生成文档 public class Doc {String name;/*** * param name* return* throw…

Python可视化集大成之作 - Seaborn 介绍

我今天要介绍一款让你在数据可视化中游刃有余的利器——Seaborn包。作为Python数据可视化库中的一员,Seaborn不仅美观易用,而且功能丰富,是生物信息学中数据探索的好帮手。 为什么选择Seaborn? 1. 美观简洁 Seaborn的默认主题和颜…

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下&a…

PMP–一、二、三模–分类–变更–技巧–敏捷变更

文章目录 技巧高频考点分析(一、过程;二、人员)一、过程:1.1 变更管理:1.1.1 瀑布型变更(一次交付、尽量限制、确定性需求 >风险储备)1.1.2 敏捷型变更(多次交付、拥抱变…

mybatis框架基础以及自定义插件开发

文章目录 框架概览框架预览MyBatis框架的核心组件MyBatis框架的工作原理MyBatis框架的配置MyBatis框架的最佳实践 自定义插件开发1. 添加依赖2. 创建插件类3. 配置插件4. 启动类中注册插件5. 测试插件 参考文献 框架概览 MyBatis是一个优秀的持久层框架,它支持自定…

多个vue项目部署到nginx服务器

文章目录 需求一、项目打包1.vue.config.js2.request.js文件3.打包 二、nginx配置 需求 同一个域名安装多个vue项目。 比如:域名为 https://domain.com 后缀。那么通过不同的后缀就能去访问不同的项目地址。 https://domain.com,不加任何后缀&#x…

OBItools:Linux下的DNA条形码分析神器

在生物信息学领域,DNA条形码分析是一种非常常见的研究方法,用于物种鉴定、生态学和进化生物学研究。今天要介绍的工具就是专为此设计的——OBItools。这个工具集专门用于处理生态学和进化生物学中的DNA条形码数据,在Linux环境下运行。无论你是…

linux下进行lvm分区及扩容

目录 LVM存储管理介绍 lvm磁盘扩容有两种方式 创建lvm磁盘 1. 首先先加入第一块儿新的磁盘 2. 对新磁盘 /dev/sdb 进行分区 通过LVM命令创建新卷 1. 创建物理卷 2.创建卷组 并将物理卷加入其中 3. 创建逻辑卷并分配大小 4.格式化刚刚创建的硬盘 5. 挂载磁盘 扩容lvm…

《Web性能权威指南》-网络技术概览-读书笔记

注:TCP/IP等知识牵涉面太广,且不说本文,哪怕是原书,限于篇幅,很多知识点都是大致介绍下。如果想深入理解,需要更一步Google相关页面资料。 延迟与带宽 WPO,Web Performance Optimization&…

基于苹果Vision Pro的AI NeRF方案:MetalSplatter

随着苹果Vision Pro的发布,混合现实(Mixed Reality, MR)技术迎来了一个新的发展阶段。为了充分利用Vision Pro的潜力,一款名为MetalSplatter的Swift/Metal库应运而生,它允许开发者在Vision Pro上以全立体的方式体验捕捉内容。本文将详细介绍MetalSplatter的特点及其如何为…