Unity DOTS如何优雅地实现ECS框架下的定时器Timer系统(无对象池,零GC)

news2024/11/25 16:31:50

实现定时器并不复杂,就是写个类存放回调,再写个类来统一管理这些回调类。但是ECS写代码的方式变了,所以还是有些区别的。

实现过程中需要注意的几点:
1、由于IComponentData不能存放managed类型的数据,所以无法用常规的ECS方式来存放定时器数据,只能用一个NativeHashMap作为SystemBase里的一个字段来存数据了。

2、常规来说,NativeList肯定比NativeHashMap的内存占用小的,但是用过后发现List要在遍历过程中删除元素实在是不好看,就只能用Map了。不过影响不大。

3、试过回调的保存用c#官方的delegate,但是打包WASM到浏览器上运行,报了个错“function signature mismatch”。没有深入处理,直接放弃了该用法。也试过用Unity.Burst自带的FunctionPointer函数指针,但是有个限制,函数不能作为类的实例方法,只能是static的静态方法,而static方法遍历IComponentData很不方便,也只能放弃。

4、目前的实现方法有个小小的瑕疵,但不影响功能。就是每个SystemBase里只能实现一个定时器回调,如果需要实现一个每2秒执行一次的回调,和一个每1秒执行一次的回调,就不行,只能分开在两个SystemBase里。由于SystemBase是class来的,太多的话,创建和销毁会涉及到GC,对性能有一点影响。

不过新版的Entities有了ISystem,是struct类型的,可以随便创建删除,且可以完全BurstCompile的。ProjectTiny无法更新到新版,只能先这样用着。用法跟MonoBehavior实现的差不多,可以添加任意间隔,重复执行的回调,也可以添加指定执行几次或间隔执行一次的回调。看代码:

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using System.Runtime.CompilerServices;
using System;

namespace wangtal.ClockSystem {
    #pragma warning disable 660, 661
    [BurstCompile]
    public readonly struct ClockHandle : IEquatable<ClockHandle>{
        private static uint _uid = 1;
        internal static uint uid => _uid++;
        public static ClockHandle Null = new ClockHandle(0);

        private readonly uint Id;

        internal ClockHandle(uint id) {
            Id = id;
        }

        bool IEquatable<ClockHandle>.Equals(Tiny3D.ClockHandle other) {
            return this == other;
        }

        public static bool operator ==(ClockHandle left, ClockHandle right) {
            return left.Id == right.Id;
        }

        public static bool operator !=(ClockHandle left, ClockHandle right)
        {
            return left.Id != right.Id;
        }

        public override string ToString() {
            return $"id:{Id}";
        }

        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }
    }
    #pragma warning restore 660, 661

    public interface IClockExecutor {
        void Execute();
    }

    // public delegate void Callback(/*EntityManager entityManager*/);

    [BurstCompile]
    internal struct Clock {
        internal readonly ClockHandle Handle;

        // function signature mismatch
        // internal readonly /*FunctionPointer<Callback>*/Callback FunctionPointer;
        internal readonly IClockExecutor Executor;

        // 负数代表无限循环,0代表结束循环了,大于0代表重复执行次数
        internal int Repeat;

        // 毫秒数
        internal double StartTime;

        // 毫秒
        internal readonly int Interval;

        public Clock(ClockHandle handle, /*FunctionPointer<Callback>*/IClockExecutor executor, int repeat, double startTime, int interval) {
            Handle = handle;
            Executor = executor;
            Repeat = repeat;
            StartTime = startTime;
            Interval = interval;
        }
    }

    public class ClockSystem : SystemBase
    {
        private NativeHashMap<ClockHandle, Clock> clocks;

        protected override void OnCreate()
        {
            base.OnCreate();
            clocks = new NativeHashMap<ClockHandle, Clock>(10, Allocator.Persistent);
        }

        protected override void OnUpdate()
        {
            if (clocks.Count() <= 0) {
                return;
            }

            var handles = clocks.GetKeyArray(Allocator.Temp);
            foreach (var handle in handles) {

                if (clocks.TryGetValue(handle, out var clock)) {
                    if (clock.Repeat == 0) {
                        clocks.Remove(clock.Handle);
                        // Unity.Tiny.Debug.Log("移除定时器:" + clock.Handle);
                        continue;
                    }

                    if ((Time.ElapsedTime * 1000) >= (clock.StartTime + clock.Interval)) {
                        clock.Executor.Execute();

                        clock.StartTime = Time.ElapsedTime * 1000;
                        if (clock.Repeat > 0) {
                            clock.Repeat--;
                        }
                        clocks[handle] = clock;
                    }
                }
            }
            handles.Dispose();
        }

        protected override void OnDestroy()
        {
            clocks.Dispose();
            base.OnDestroy();
        }

        public ClockHandle AddOnce(IClockExecutor executor, int interval) {
            return AddRepeat(executor, 1, interval);
        }

        public ClockHandle AddLoop(IClockExecutor executor, int interval) {
            return Add(executor, -1, interval);
        }

        public ClockHandle AddRepeat(IClockExecutor executor, int repeat, int interval) {
            return Add(executor, repeat, interval);
        }

        public bool Valid(in ClockHandle handle) {
            if (handle == ClockHandle.Null) {
                return false;
            }
            return clocks.ContainsKey(handle);
        }

        public void Remove(ref ClockHandle handle) {
            if (clocks.ContainsKey(handle)) {
                Unity.Tiny.Debug.Log("移除!!!" + handle);
                clocks.Remove(handle);
                handle = ClockHandle.Null; // 定时器被清掉了,要重置掉外部的handle
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ClockHandle Add(IClockExecutor executor, int repeat, int interval) {
            Unity.Tiny.Assertions.Assert.IsTrue(repeat != 0);
            var handle = GetClockHandle();
            Unity.Tiny.Debug.Log("创建定时器:" + handle);
            // var fp = GetBurstedFunction(executor);
            var startTime = Time.ElapsedTime * 1000;
            var clock = new Clock(handle, /*fp*/executor, repeat, startTime, interval);
            clocks.Add(handle, clock);
            return handle;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ClockHandle GetClockHandle() {
            var uid = ClockHandle.uid;
            for (uint i = 1; i <= uid; i++) { // 遍历一遍已经无效的ID,复用它
                var handle = new ClockHandle(i);
                if (!clocks.ContainsKey(handle)) {
                    return handle;
                }
            }

            Unity.Tiny.Assertions.Assert.IsTrue(false, "不应该走到这里");
            return new ClockHandle(ClockHandle.uid);
        }

        // 用函数指针会有问题,且会限制回调函数只能为static的。直接用delegate也会有问题,报错:function signature mismatch
        // [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        // private FunctionPointer<Callback> GetBurstedFunction(Callback executor) {
        //     // return BurstCompiler.CompileFunctionPointer<Callback>(executor);
        //     return new FunctionPointer<Callback>(Marshal.GetFunctionPointerForDelegate(executor));
        // }
    }
}

用法的示例代码:

public class TestLoopClock : SystemBase, IClockExecutor
    {
        private ClockSystem clockSystem;

        private int cnt;

        private ClockHandle timer;

        protected override void OnCreate()
        {
            base.OnCreate();
            clockSystem = World.GetOrCreateSystem<ClockSystem>();
            timer = clockSystem.AddLoop(this, 1000); // 每隔1秒就执行1次,不停执行
            cnt = 0;
        }
        
        void IClockExecutor.Execute() {
        	// 达到某些条件后,在回调里把定时器移除掉
            //if (cnt > 10) {
                //clockSystem.Remove(ref timer);
                //return;
            //}

            Entities.ForEach((Entity e, ref Unity.Tiny.Text.TextRenderer tr, in Unity.Tiny.UI.UIName uiName) =>
            {
                if (uiName.Name == "txtResolution")
                {
                    Unity.Tiny.Text.TextLayout.SetEntityTextRendererString(EntityManager, e, $"loop {cnt++}");
                }
            }).WithStructuralChanges().WithoutBurst().Run();
        }

        protected override void OnUpdate()
        {
        }
    }

    public class TestOnceClock : SystemBase, IClockExecutor
    {
        private ClockSystem clockSystem;

        private ClockHandle timer;

        protected override void OnCreate()
        {
            base.OnCreate();
            clockSystem = World.GetOrCreateSystem<ClockSystem>();
            timer = clockSystem.AddOnce(this, 3000); // 延迟3秒执行一次就停止
        }

        void IClockExecutor.Execute() {
            Entities.ForEach((Entity e, ref Unity.Tiny.Text.TextRenderer tr, in Unity.Tiny.UI.UIName uiName) =>
            {
                if (uiName.Name == "txtOnce")
                {
                    Unity.Tiny.Text.TextLayout.SetEntityTextRendererString(EntityManager, e, "Once END!");
                }
            }).WithStructuralChanges().WithoutBurst().Run();
        }

        protected override void OnUpdate()
        {
        }
    }

    public class TestRepeatClock : SystemBase, IClockExecutor
    {
        private ClockSystem clockSystem;

        private ClockHandle timer;

        private int cnt;

        protected override void OnCreate()
        {
            base.OnCreate();
            clockSystem = World.GetOrCreateSystem<ClockSystem>();
            timer = clockSystem.AddRepeat(this, 5, 1000); // 间隔1秒,执行5次停止
            cnt = 0;
        }

        void IClockExecutor.Execute() {
            Entities.ForEach((Entity e, ref Unity.Tiny.Text.TextRenderer tr, in Unity.Tiny.UI.UIName uiName) =>
            {
                if (uiName.Name == "txtRepeat")
                {
                    Unity.Tiny.Text.TextLayout.SetEntityTextRendererString(EntityManager, e, $"Repeat {cnt++}");
                }
            }).WithStructuralChanges().WithoutBurst().Run();
        }

        protected override void OnUpdate()
        {
        }
    }

打包WASM到浏览器上的运行效果:
在这里插入图片描述

由于ECS框架下没有协程,也没有InvokeRepeat这样的方法,如果在OnUpdate里每帧自己计时来执行回调,所有人都得处理好这些细节,这样一点也不好用,所以自己实现一个统一管理的定时器会方便很多。

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

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

相关文章

微服务架构+创建微服务工程(商品/订单)

目录 1.微服务架构 1.1.单体应用架构 1.2.微服务应用 1.3 微服务架构的优势 1.4.微服务架构的缺点(挑战) 1.5. SpringCloud与微服务关系 1.6.SpringBoot和SpringCloud关系 2. 创建微服务工程 2.1.数据库 2.2.搭建父工程 2.2 创建公共模块 2.3.商品系统 2.4.订单微…

【通讯协议备忘录】stm32的CAN外设

文章目录 帧结构测试模式&#xff08;静默/换回/环回静默&#xff09;&#xff1a;环回测试配置 过滤器的使用测试参考用例过滤器的初始化发送和接收 中断 帧结构 CAN的报文结构&#xff1a; 测试模式&#xff08;静默/换回/环回静默&#xff09;&#xff1a; 静默模式&…

Apache(httpd) 搭建笔记

Apache 搭建笔记 安装Apache HTTP服务器&#xff1a;启动Apache服务并设置开机自启 配置SSL证书配置Apache的SSL虚拟主机&#xff1a;重启Apache服务以使更改生效&#xff1a; 多站点配置第一个虚拟主机配置第二个虚拟主机创建每个站点的根目录&#xff1a; 强制跳转http>&g…

mysql及事务隔离级别

目录 一 事务之间相互影响分为几种 二 mysql常见的储存引擎 三 死锁 四 查看使用的储存引擎 五 修改储存引擎 六 总结 一 事务之间相互影响分为几种 脏读:就是读取了没有提交的数据, 不可重复读 :前后多次读取内容不一致 幻读:两次读的结果不一样 丢失更新:后一个会覆…

哈夫曼编码(霍夫曼、赫夫曼)

一、发展历史 哈夫曼使用自底向上的方法构建二叉树。 哈夫曼编码的基本方法是先对图像数据扫描一遍&#xff0c;计算出各种像素出现的概率&#xff0c;按概率的大小指定不同长度的唯一码字&#xff08;这种长度不同的编码方式称为变长编码&#xff0c;对应的长度相同的编码方…

关闭Vue CLI(脚手架)中的语法检查

1.创建一个名为vue.config.js的文件&#xff0c;与package.json文件平级 参考官方文档&#xff1a;Home | Vue CLI (vuejs.org) 2.将下面代码复制进vue.config.js文件中 module.exports{lintOnSave:false;//关闭语法检查 }

c++计算贝塞尔曲线(折线平滑为曲线)坐标方法

效果可查看上一篇博文&#xff1a;js手动画平滑曲线&#xff0c;贝塞尔曲线拟合【代码】js手动画平滑曲线&#xff0c;贝塞尔曲线拟合。https://blog.csdn.net/qiufeng_xinqing/article/details/131711963?spm1001.2014.3001.5502 代码如下&#xff1a; #include <cmath&…

10.6.1 【Linux】撷取命令: cut, grep

cut cut 主要的用途在于将“同一行里面的数据进行分解&#xff01;”最常使用在分析一些数据或文字数据的时候。这是因为有时候我们会以某些字符当作分区的参数&#xff0c;然后来将数据加以切割&#xff0c;以取得我们所需要的数据。 grep 10.6.2 排序命令&#xff1a; sort,…

最火爆的大模型框架LangChain七大核心及案例剖析上(一)

最火爆的大模型框架LangChain七大核心及案例剖析上 10.1 Models解析及案例剖析 本节正式进入当前开源界最火爆的大模型开发框架LangChain的部分,会讲解整个LangChain解决的问题及它的工作机制,通过一个“LangChain GPT内容创建者”(“LangChain GPT Content Creator”)的具体…

第九章:RefineNet——多路径细化网络用于高分辨率语义分割

0.摘要 最近&#xff0c;非常深的卷积神经网络&#xff08;CNN&#xff09;在目标识别方面表现出色&#xff0c;并且也是密集分类问题&#xff08;如语义分割&#xff09;的首选。然而&#xff0c;在深度CNN中&#xff0c;重复的子采样操作&#xff08;如池化或卷积跳跃&#x…

Matplotlib figure图形对象

通过前面的学习&#xff0c;我们知道matplotlib.pyplot模块能够快速地生成图像&#xff0c;但如果使用面向对象的编程思想&#xff0c;我们就可以更好地控制和自定义图像。 在 Matplotlib 中&#xff0c;面向对象编程的核心思想是创建图形对象&#xff08;figure object&#…

第十三章——类继承

面向对象编程的主要目的之一是提供可重用的代码。&#xff08;重用经过测试的代码比重新编写代码要好的多&#xff09; C类提供了更高层次的重用性。很多厂商提供了类库&#xff0c;类组合了数据表示和类方法&#xff0c;因此提供了比函数库更加完整的程序包。通常类库是以源代…

文件IO_文件读写(附Linux-5.15.10内核源码分析)

目录 1.什么是文件偏移量&#xff1f; 1.1 文件偏移量介绍 1.2 文件偏移量重点 1.3 文件偏移量工作原理 2.文件偏移量设置 2.1 lseek函数 2.2 lseek内核源码分析 3.写文件 3.1 write函数 3.2 write内核源码分析 4.读文件 4.1 read函数 4.2 read内核源码分析 5.文…

物流难统计、难管理?学会这招,问题迎刃而解

在当今数字化的时代&#xff0c;商家面临着诸多物流挑战。物流数据人工统计成本高、管理难、决策难是很常见的问题。本文将探讨如何通过智能数据分析解决这些问题&#xff0c;帮助商家提升物流效率和管理能力。 物流数据人工统计&#xff0c;难决策 物流数据沉淀全靠人工线下…

Vue3_简介、CompositionVPI、新的组件

文章目录 Vue3快速上手1.Vue3简介2.Vue3带来了什么1.性能的提升2.源码的升级3.拥抱TypeScript4.新的特性 一、创建Vue3.0工程1.使用 vue-cli 创建2.使用 vite 创建 二、常用 Composition API1.拉开序幕的setup2.ref函数3.reactive函数4.Vue3.0中的响应式原理vue2.x的响应式Vue3…

labview实现呼吸灯颜色渐变效果

呼吸灯效果具有美好的视觉观感&#xff0c;前一段时期感受了一位大佬在MCU中实现呼吸灯颜色渐变效果&#xff0c;很是震撼。这引起了我的兴趣&#xff0c;本文则是实现一种呼吸灯效果(主要在于颜色的渐变体现)。 程序整体视图 程序框图 公式节点程序 int red_is_0 red 0 ?…

探索MR与AIGC技术的发展机遇:教育、医疗领域的前景展望

在当今科技迅猛发展的时代&#xff0c;混合现实&#xff08;MR&#xff09;和增强智能生成创作&#xff08;AIGC&#xff09;技术正逐渐成为教育、医疗领域中的关键驱动力。这两项前沿技术的结合为我们带来了无限的可能性和创新的机遇。 MR技术在教育领域中的发展与机遇是广泛而…

非洲秃鹫优化算法(AVOA)(含MATLAB代码)

先做一个声明&#xff1a;文章是由我的个人公众号中的推送直接复制粘贴而来&#xff0c;因此对智能优化算法感兴趣的朋友&#xff0c;可关注我的个人公众号&#xff1a;启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法&#xff0c;经典的&#xff0c;或者是近几年…

LayUI框架实现OA会议系统——增删改查

目录 前言 1. 配置准备 1.1 Layui框架 1.2 mysql数据库表 1.3 用户管理JSP页面 1.4 新增、修改用户共用界面 2. 后台编写 2.1 编写UserDao类增删改查方法 2.2 R工具类 2.3 BaseDao数据库查询方法 2.4 UserAction控制器类 3. 前台JS编写 3.1 userManage页面JS 3.2…

测试报告?Python自动化测试-Allure测试报告使用大全,一篇全通透

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 安装并配置环境变…