CocosCreator组件上的schedule

news2025/1/15 13:40:09

目录

1.首先看component.ts中schedule 函数,核心代码就是获取director.getScheduler(),并调用schedule方法,把callback等参数传递进去。

2.再看到scheduler.ts类中的schedule方法,只取一些主要代码,下面会分段详细拆解下面的代码:

2.1.根据组件的uuid或id,获取一个HashTimer。

2.1.1此中的_hashForTimers是什么?就是一个v8下的字典模式,主要方法就是Object.create(null),创建一个无原型链的对象,绕过一些检测机制,不需要用hasOwnProperty来判断对象内是否存在某元素,直接用_hashForTimers["anyString"],若不存在该值,则值为undefine

2.1.2.这里的HashTimerEntry类里包含了:

2.2.若无element,就新建一个,并push到一个数组中_arrayForTimers。另存数组里是为方便遍历调用,_hashForTimers是为了方便通过uuid直接取到对象。

2.3.创建timers数组,准备存入callback方法,若callback方法已经存在,只改变调用间隔。

2.4.从缓存池里pop出一个CallbackTimer对象,若无就新建一个,填充数据后push到timers中。

3.加入的全过程看完了,那么怎么触发callback呢?

4.再看到scheduler中的update函数:

5.跳转到 CallbackTimer 类:

6.trigger 和 cancel 函数:

7.看到scheduler 的 unschedule 函数:


1.首先看component.ts中schedule 函数,核心代码就是获取director.getScheduler(),并调用schedule方法,把callback等参数传递进去。

    public schedule (callback, interval = 0, repeat: number = legacyCC.macro.REPEAT_FOREVER, delay = 0) {
        assertID(callback, 1619);

        interval = interval || 0;
        assertID(interval >= 0, 1620);

        repeat = Number.isNaN(repeat) ? legacyCC.macro.REPEAT_FOREVER : repeat;
        delay = delay || 0;

        const scheduler = legacyCC.director.getScheduler();

        // should not use enabledInHierarchy to judge whether paused,
        // because enabledInHierarchy is assigned after onEnable.
        // Actually, if not yet scheduled, resumeTarget/pauseTarget has no effect on component,
        // therefore there is no way to guarantee the paused state other than isTargetPaused.
        const paused = scheduler.isTargetPaused(this);

        scheduler.schedule(callback, this, interval, repeat, delay, paused);
    }

2.再看到scheduler.ts类中的schedule方法,只取一些主要代码,下面会分段详细拆解下面的代码:

    public schedule (callback: (dt?: number) => void, target: ISchedulable, interval: number, repeat?: number, delay?: number, paused?: boolean) {
        ...
        const targetId = target.uuid || target.id;
        ...
        let element = <HashTimerEntry> this._hashForTimers[targetId];
        if (!element) {
            element = HashTimerEntry.get(null, target, 0, null, null, paused);
            this._arrayForTimers.push(element);
            this._hashForTimers[targetId] = element;
        } else if (element.paused !== paused) {
            warnID(1511);
        }

        let timer;
        let i;
        if (element.timers == null) {
            element.timers = [];
        } else {
            for (i = 0; i < element.timers.length; ++i) {
                timer = element.timers[i];
                if (timer && callback === timer._callback) {
                    logID(1507, timer.getInterval(), interval);
                    timer._interval = interval;
                    return;
                }
            }
        }

        timer = CallbackTimer.get();
        timer.initWithCallback(this, callback, target, interval, repeat, delay);
        element.timers.push(timer);

        if (this._currentTarget === element && this._currentTargetSalvaged) {
            this._currentTargetSalvaged = false;
        }
    }

2.1.根据组件的uuid或id,获取一个HashTimer。

const targetId = target.uuid || target.id;
let element = <HashTimerEntry> this._hashForTimers[targetId];
2.1.1此中的_hashForTimers是什么?就是一个v8下的字典模式,主要方法就是Object.create(null),创建一个无原型链的对象,绕过一些检测机制,不需要用hasOwnProperty来判断对象内是否存在某元素,直接用_hashForTimers["anyString"],若不存在该值,则值为undefine
export function createMap (forceDictMode?: boolean): any {
    const map = Object.create(null);
    if (forceDictMode) {
        const INVALID_IDENTIFIER_1 = '.';
        const INVALID_IDENTIFIER_2 = '/';
        // assign dummy values on the object
        map[INVALID_IDENTIFIER_1] = 1;
        map[INVALID_IDENTIFIER_2] = 1;
        delete map[INVALID_IDENTIFIER_1];
        delete map[INVALID_IDENTIFIER_2];
    }
    return map;
}
this._hashForTimers = createMap(true);
2.1.2.这里的HashTimerEntry类里包含了:
    constructor (timers: any, target: ISchedulable, timerIndex: number, currentTimer: any, currentTimerSalvaged: any, paused: any) {
        this.timers = timers;
        this.target = target;
        this.timerIndex = timerIndex;
        this.currentTimer = currentTimer;
        this.currentTimerSalvaged = currentTimerSalvaged;
        this.paused = paused;
    }

2.2.若无element,就新建一个,并push到一个数组中_arrayForTimers。另存数组里是为方便遍历调用,_hashForTimers是为了方便通过uuid直接取到对象。

if (!element) {
            // Is this the 1st element ? Then set the pause level to all the callback_fns of this target
            element = HashTimerEntry.get(null, target, 0, null, null, paused);
            this._arrayForTimers.push(element);
            this._hashForTimers[targetId] = element;
        }

2.3.创建timers数组,准备存入callback方法,若callback方法已经存在,只改变调用间隔。

        if (element.timers == null) {
            element.timers = [];
        } else {
            for (i = 0; i < element.timers.length; ++i) {
                timer = element.timers[i];
                if (timer && callback === timer._callback) {
                    logID(1507, timer.getInterval(), interval);
                    timer._interval = interval;
                    return;
                }
            }
        }

2.4.从缓存池里pop出一个CallbackTimer对象,若无就新建一个,填充数据后push到timers中。

主要数据有:

  • _scheduler:scheduler自身,主要用来调用unschedule来取消。
  • _target :函数调用主体,主要用来在调用callback时传入调用.call传入调用主体。
  • _callback :调用函数。
  • _interval :触发间隔时间。
  • _elapsed:已经过去的时间。
  • _delay :延迟触发时间。
  • _useDelay :使用延迟触发标记。
  • _repeat :执行函数调用次数。
  • _runForever:一直循环调用标记。

这里还有一个put函数,把该回收的对象放入到缓存池中,看到这里的代码可以为我们提供一些编写代码的思路,对于缓存池需要有一个最大池数量和一个锁标记。

class CallbackTimer {
    public static get = () => CallbackTimer._timers.pop() || new CallbackTimer()
    public static put = (timer: CallbackTimer | any) => {
        if (CallbackTimer._timers.length < MAX_POOL_SIZE && !timer._lock) {
            timer._scheduler = timer._target = timer._callback = null;
            CallbackTimer._timers.push(timer);
        }
    }    
    public initWithCallback (scheduler: any, callback: any, target: ISchedulable, seconds: number, repeat: number, delay: number) {
        this._lock = false;
        this._scheduler = scheduler;
        this._target = target;
        this._callback = callback;

        this._elapsed = -1;
        this._interval = seconds;
        this._delay = delay;
        this._useDelay = (this._delay > 0);
        this._repeat = repeat;
        this._runForever = (this._repeat === legacyCC.macro.REPEAT_FOREVER);
        return true;
    }        
}

timer = CallbackTimer.get();
timer.initWithCallback(this, callback, target, interval, repeat, delay);
element.timers.push(timer);

3.加入的全过程看完了,那么怎么触发callback呢?

回到director类中,能看到在init类中,把 this._scheduler push到 this._systems 中,然后在tick(主循环)中遍历 this._systems 调用 scheduler.update 函数。

export class Director extends EventTarget {
    public init () {
        ...
        this.registerSystem(Scheduler.ID, this._scheduler, 200);
        ...
    }
    /**
     * @en Register a system.
     * @zh 注册一个系统。
     */
    public registerSystem (name: string, sys: System, priority: number) {
        sys.id = name;
        sys.priority = priority;
        this._systems.push(sys);
        this._systems.sort(System.sortByPriority);
    }
    public tick (dt: number) {
        ...
        for (let i = 0; i < this._systems.length; ++i) {
            this._systems[i].update(dt);
        }
        ...
    }
}

4.再看到scheduler中的update函数:

这里先说一个题外话,在update函数中,还包含了有优先级区分的 scheduleUpdate 函数push进来的调用处理,这里不细说。

遍历 this._arrayForTimers,再遍历 每一个元素中的 timers ,调用 timers[i] 的 updeate 函数,也就是 CallbackTimer 类中的 updeate 函数。

    public update (dt) {
       ...
        // Iterate over all the custom selectors
        let elt;
        const arr = this._arrayForTimers;
        for (i = 0; i < arr.length; i++) {
            elt = <HashTimerEntry> arr[i];
            this._currentTarget = elt;
            this._currentTargetSalvaged = false;

            if (!elt.paused) {
                // The 'timers' array may change while inside this loop
                for (elt.timerIndex = 0; elt.timerIndex < elt.timers.length; ++(elt.timerIndex)) {
                    elt.currentTimer = elt.timers[elt.timerIndex];
                    elt.currentTimerSalvaged = false;

                    elt.currentTimer.update(dt);
                    elt.currentTimer = null;
                }
            }

            // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
            if (this._currentTargetSalvaged && this._currentTarget.timers.length === 0) {
                this._removeHashElement(this._currentTarget);
                --i;
            }
        }
        ...
    }

5.跳转到 CallbackTimer 类:

首先 this._elapsed 加上 dt,再判断是否满足间隔触发时间,延迟时间,循环次数,若满足就调用 this.trigger() 函数,在触发后若不满足继续条件,就调用 this.cancel() 销毁自身。

    public update (dt: number) {
        if (this._elapsed === -1) {
            this._elapsed = 0;
            this._timesExecuted = 0;
        } else {
            this._elapsed += dt;
            if (this._runForever && !this._useDelay) { // standard timer usage
                if (this._elapsed >= this._interval) {
                    this.trigger();
                    this._elapsed = 0;
                }
            } else { // advanced usage
                if (this._useDelay) {
                    if (this._elapsed >= this._delay) {
                        this.trigger();

                        this._elapsed -= this._delay;
                        this._timesExecuted += 1;
                        this._useDelay = false;
                    }
                } else if (this._elapsed >= this._interval) {
                    this.trigger();

                    this._elapsed = 0;
                    this._timesExecuted += 1;
                }

                // @ts-expect-error Notes written for over eslint
                if (this._callback && !this._runForever && this._timesExecuted > this._repeat) {
                    this.cancel();
                }
            }
        }
    }

6.trigger 和 cancel 函数:

trigger函数就是调用callback函数,调用的方法是 this._callback.call(this._target, this._elapsed) 显式地设置函数的上下文;并且在调用过程中给 _lock 值加了锁,这样在函数调用过程中就不会被 清理掉。

cancel函数就是调用 scheduler 的 unschedule 函数。

    public trigger () {
        if (this._target && this._callback) {
            this._lock = true;
            this._callback.call(this._target, this._elapsed);
            this._lock = false;
        }
    }

    public cancel () {
        // override
        this._scheduler.unschedule(this._callback, this._target);
    }

7.看到scheduler 的 unschedule 函数:

通过uuid或id获取元素,遍历其 timers 对象,找到 callback 对应位置,把此对象 put 到缓存池中,等待后续有需要时重新拿出来用。

    public unschedule (callback, target: ISchedulable) {
        if (!target || !callback) {
            return;
        }
        const targetId = target.uuid || target.id;
        if (!targetId) {
            errorID(1510);
            return;
        }

        const element = this._hashForTimers[targetId];
        if (element) {
            const timers = element.timers;
            for (let i = 0, li = timers.length; i < li; i++) {
                const timer = timers[i];
                if (callback === timer._callback) {
                    if ((timer === element.currentTimer) && (!element.currentTimerSalvaged)) {
                        element.currentTimerSalvaged = true;
                    }
                    timers.splice(i, 1);
                    CallbackTimer.put(timer);
                    if (element.timerIndex >= i) {
                        element.timerIndex--;
                    }

                    if (timers.length === 0) {
                        if (this._currentTarget === element) {
                            this._currentTargetSalvaged = true;
                        } else {
                            this._removeHashElement(element);
                        }
                    }
                    return;
                }
            }
        }
    }

 如果觉得有帮助请给我点赞并收藏哦~您的支持是我最大的鼓励~

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

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

相关文章

dockerfile 例子(二)

Dockerfile由一行一行的命令语句组成&#xff0c;#开头的为注释行。Dockerfile文件内容分为四个部分&#xff1a;基础镜像信息、维护者信息、镜像操作指令以及容器启动执行指令。 接下来给大家列出Dockerfile中主要命令的说明。 FROM&#xff0c;指定所创建镜像的基础镜像。 …

Verilog基础:块语句

相关阅读 Verilog基础专栏https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 1、块语句 块语句(block statements)是一种把语句组织在一起&#xff0c;这样他们在语法上就像单个语句一样工作。Verilog HDL中有两种类型的块&#xff1a; …

“惠医通-医院挂号订单平台”

结合已学习过的vue3和TS完成的项目&#xff0c;便于患者对自己想要就诊的科室进行挂号&#xff0c;付款 一&#xff1a;项目简介 前端技术栈 Vue3 TS vue-router Element-ui Axios Pinia 项目架构 二&#xff1a;主要模块 1. axios二次封装 1.1 创建实例 //利用axios.creat…

如何调整DOSBOX软件的运行窗口大小

前言 小编最近正在学习微机原理&#xff0c;碰到一些问题&#xff0c;在安装DOSBOX后&#xff0c;打开应用&#xff0c;会出现运行窗口特别小&#xff0c;字体也很小的情况&#xff0c;使用时会感觉特别费劲&#xff0c;看着特别的不舒服&#xff0c;那么这个时候就需要调整一…

【LeetCode题目详解】第八章 贪心算法 part01 理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和 day31补

贪心算法理论基础 关于贪心算法&#xff0c;你该了解这些&#xff01; 题目分类大纲如下&#xff1a; # 什么是贪心 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 这么说有点抽象&#xff0c;来举一个例子&#xff1a; 例如&#xff0c;有一堆钞票&…

【C#】C#:“指派给常量数组的必须是常量”

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 记录一个有意思的代码片段。 首先&#xff0c;复习一下常量。…

安防监控/磁盘阵列存储/视频汇聚平台EasyCVR调用rtsp地址返回的IP不正确是什么原因?

安防监控/云存储/磁盘阵列存储/视频汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等&#xff0c;能对外分发RTSP、RT…

leetcode 563.二叉树的坡度

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;https://leetcode.cn/problems/binary-tree-tilt/description/ 代码&#xff1a; class Solution { public:int childFind(TreeNode* root , int& sumTile) {if (root nullptr) {return 0; // 空树坡度为0}int l…

VBA技术资料MF48:VBA_在Excel中将列号与字母转换

【分享成果&#xff0c;随喜正能量】除非自己的认知获得了改变和刷新&#xff0c;否则&#xff0c;人们常说的“顺应自己的内心”&#xff0c;顺的不过是一颗旧心&#xff0c;一颗惯性的&#xff0c;充满了各种习性的套路之心。与“顺应自己的内心”恰恰相反&#xff0c;人要用…

2023最新独立版校园跑腿校园社区小程序系统源码 | 附教程

2023最新独立版校园跑腿校园社区小程序系统源码 | 附教程 测试环境&#xff1a;NginxPHP7.2MySQL5.6 多校版本&#xff0c;多模块&#xff0c;适合跑腿&#xff0c;外卖&#xff0c;表白&#xff0c;二手&#xff0c;快递等校园服务 源码下载&#xff1a;https://download.c…

二叉搜索树(C++)

二叉搜索树 概念二叉搜索树的应用二叉搜索树的实现K模型基本结构和函数声明接口实现①find——查找关键码②Insert——插入关键码③Erase——删除关键码&#xff08;重点&#xff09;时间复杂度 源码&#xff08;整体&#xff09;非递归递归 KV模型 在使用C语言写数据结构阶段时…

【Linux】进程通信 — 信号(下篇)

文章目录 &#x1f4d6; 前言1. 阻塞信号1.1 信号其他相关常见概念&#xff1a;1.2 sigset_t&#xff1a;1.2 - 1 信号集操作函数 1.3 sigprocmask&#xff1a;1.4 sigpending&#xff1a; 2. 进程处理信号2.1 内核页表和用户页表&#xff1a;2.2 内核态和用户态&#xff1a;2.…

华为OD机试 - VLAN资源池 - 回溯、双指针(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路1、核心思想2、具体解题思路 五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&…

产品帮助中心对SaaS行业的作用

帮助中心是一款SaaS产品必不可少的一部分&#xff0c;为了帮助用户更好的解决产品相关问题&#xff0c;提高新用户的使用体验&#xff0c;并且引导其更好地使用产品。 所以今天我们就来谈谈帮助中心对SaaS行业的作用&#xff0c;以及制作帮助中心的方法&#xff0c;每个产品帮…

基于 OV5640 的图像采集显示系统(DVP 接口时序逻辑设计)

文章目录 前言一、DVP 接口时序逻辑设计二、基本数据流接收三、像素位置输出四、舍弃前 N 张图像五、系统异常状态恢复控制六、完整代码展示七、仿真代码展示八、仿真波形展示前言 上一节,我们已经完成了 OV5640 初始化逻辑的介绍。接下来,将要开始完成 DVP 接口的时序设计。…

Maven入门教程(一):安装Maven环境

Maven项目对象模型(POM)&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的软件项目管理工具。 ​ 在项目开发中Maven可以对jar包和对工程之间的依赖关系进行管理。maven仓库中存储jar包&#xff0c;可以一次下载&#xff0c;所有项目通用。 1. 安装…

Java项目-苍穹外卖-Day07-redis缓存应用-SpringCache/购物车功能

文章目录 前言缓存菜品问题分析和实现思路缓存菜品数据清理缓存数据功能测试 SpringCache介绍入门案例 缓存套餐 前言 本章节主要是进行用户端的购物车功能开发 和redis作为mysql缓存的应用以及SpringCache的介绍 因为很多人查询数据库会导致mysql的查询效率降低&#xff0c;可…

解读亚马逊云科技语义搜图检索方案

图像检索&#xff08;包括文搜图和图搜图&#xff09;是各个行业中常见的一个应用场景。比如在电商场景中&#xff0c;基于以图搜图做相似商品查找&#xff1b;在云相册场景中&#xff0c;基于文搜图来找寻所需的图像素材。 传统基于标签的图像检索方式&#xff0c;即先使用目标…

压力传感器的性能技术指标

压力传感器采用第四代无线传输方式&#xff0c;加入用高性能的感压芯片&#xff0c;配合先进的电路处理和温度补偿技术&#xff0c;选用不锈钢外壳做隔离防腐&#xff0c;能够测量与接触部分材质相兼容的气体和液体等介质的表压和绝压。 应用场合&#xff1a;如供水、排水、消…

如何空手套白狼?一口气省7K再抓住一个7K起步的工作?

今日话题&#xff0c;教你如何省七千再得到一个七千起步的技能&#xff01;现在网络行业已经是全世界重点发展的目标&#xff0c;开发行业更是各个企业重点培养&#xff0c;但是在学校教的网络知识太基础太老掉牙&#xff1f;报班随便就是小一万该如何是好呢&#xff1f;解决方…