【bigo前端】egret中的对象池浅谈

file

本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注、转载。

egret是一款小游戏开发引擎,支持跨平台开发,之前使用这款引擎开发了一款捕鱼游戏,在这里简单聊下再egret中关于对象池的使用,虽然该引擎已经停止维护了,但是对象池的概念适合通用的游戏场景,不限框架。

起因

关于对象池的使用主要是有两个场景,一个是捕鱼游戏里面鱼的生成,还有一个是射击过程中子弹的生成。当用户处于游戏时,这两个场景会频繁的生成对象并展示对应的动画,会造成大量的内存碎片以及频繁的分配内存空间。而通过对象池能够比较好的解决这个问题。
捕鱼场景

对象池模式

首先定义一个池对象,用来存储可复用的对象。池对象包含两组列表,一组为可复用hash对象,一组为正在使用的hash对象。在游戏加载游戏资源的时候,对池对象进行初始化,创建了一个鱼对象集合,并且同时初始化可复用hash对象作为备用池。

当游戏资源加载完成的同时,向对象池尝试获取一个可复用的鱼对象,对象池查询可复用hash对象是否存在可以复用的鱼对象,存在可复用对象的时候将对象进行初始化并将对象的索引从可复用hash对象中移除加入正在使用的hash对象。当鱼对象在游戏舞台中被移除的时候,将其进行回收到可复用hash对象等待下一轮渲染。通过这种方式避免了大量的重复对象的创建跟销毁,减少不必要的内存分配。

实现方式

首先设计一个工厂方法,实现一个FishGenerator类,负责各种类型Fish的生成,Distributor类负责各类鱼的回收与存储。

流程图

鱼的实现如下,前期开发过程中,不同种类的鱼没有单独分开类实现,都是通过传入骨骼动画与类型内部实现的,所以这里通过 type 来识别不同的鱼进行获取与复用。 hashc egret 本身的 hashCode ,通过 hashc 允许外部访问,并且由于其具有唯一性,所以将其作为存储管理的下标。

interface IFish {
     hashc:number; //hashCode
     type:string; //类型标识
     isIdle:boolean; //标记是否空闲
     lastUsed: number // 上次使用时间
     dispose():void; //释放对象内部引用
     del():void; //彻底释放对象
     reset():void;   //重置
    setProtocol( val:IDistributor ):void; //设置协议
}

Distributor 的实现如下, distribution 负责将生成或者复用的鱼分配到对应的hash对象,并删除其再原先hash对象上的引用;
clearRegularly 则是定时清除过久未复用的鱼对象,减少复用hash对象所占的内存

interface IDistributor {
    distribution(val:IFish):void; //分配
    addFish(val:IFish):void; //添加元素
    getFish(type:string):IFish; //获取元素
    clear():void; //清除所有未使用的对象
    clearRegularly(): void; // 定期清除过久未使用的对象
}

BashFish 源代码如下, reset 方法重置鱼的空闲状态,重新在空闲池跟使用池之间进行分配,记录鱼的使用时间作为定期清除的标识。 dispose 方法则是释放鱼,将鱼的状态重置为空闲状态,并将鱼重新分配。

class FishBase extends egret.Sprite implements IFish {
   private _isIdle: boolean = false;
   private _dis:IDistributor = null;
   public type:string = '';
   public lastUsed: number = 0;
    ...
    //more code
    ...
   constructor(factory: dragonBones.EgretFactory, config: fishTypeConfig) {
        super();
        this.type = config.type;
        ...
        // more code
        ...
   }
	public reset():void {
        // 重置当前鱼的对象空闲状态为false,并且更新使用时间,将其进行对象的交互
        this._isIdle = false;
        this.lastUsed = Date.now();
        this._dis.distribution(this);
	}
	public get hashc():number {
        return this.hashCode;
	}
	public get isIdle():boolean {
        return this._isIdle;
	}
  	public dispose():void {
        this._isIdle = true;
        // 重新进行分配
        this._dis.distribution(this);
	}
	public del():void {
        this.dispose();
        this._dis = null;
	}
	public setProtocol( val:IDistributor ):void {
        // 建立Distributor链接
        this._dis = val;
	}
  // 离开舞台后移除鱼
	remove() {
        this.dispose();
	}
    ...
    // more code
    ...
}
...

FishGenerator 源代码如下,FishGenerator 对外暴露expansiongetFish方法,expansion方法是当空闲池不足时,一次性创建大量的空闲的鱼备用,getFish方法是通过Distributor获取空闲池中的鱼,当空闲池中的鱼不足的时候创建新的鱼分配到空闲池中并将鱼进行返回。

class FishGenerator {
	private _dis:IDistributor = null;
    ...
    //more code
    ...
	public constructor( val:IDistributor ) {
        this.init(val);
	}
	private init(val:IDistributor):void {
        this._dis = val;
	}
    public expansion(factoryList: any, num) {
        const len = factoryList.length;
        
        for (let i = 0; i < len; i += 1) {
            const item = factoryList[i];
            const fish = this.createFish(item.factory, item.config);
            this._dis.addFish(fish);
            fish.reset();
        }
    }
	public getFish(factory: any, config:any):IFish {
        let fish:IFish = this._dis.getFish(config.type);
        // 无法进行复用,则创建一个新的鱼对象
        if(fish === null) {
            fish = this.createFish(factory, config);
            this._dis.addFish(fish);
            fish.reset();
        }
        return fish;
	}

	private createFish(factory: any, config:any):IFish {
        return new FishBase(factory, config);
	}
    ...
    //more code
    ...
}

分配器源代码如下,对外提供distributionaddFishgetFishclear以及clearRegularly方法

distribution:根据鱼的状态进行重新分配

addFish:建立鱼跟Distributor 的链接,并将鱼分配到空闲池

getFish:获取可复用的鱼

clear:清除空闲鱼群

clearRegularly: 获取鱼次数达到500的倍数的时候,清除过久未使用的空闲鱼群

class Distributor implements IDistributor {
	private _UsedPool:Object = null; //使用中的鱼
	private _IdlePool:Object = null; //空闲的鱼
    private count:number= 0;
    ...
    //more code
    ...
	public constructor() {
        this._IdlePool = {};
        this._UsedPool = {};
	}
    // 将鱼进行重新分配,空闲状态分配到空闲hash对象,使用中分配到使用hash对象
    // 移除原先的引用
	public distribution( val:IFish ):void {
        if(val.isIdle) {
            this._IdlePool[val.hashc] = val;
            delete this._UsedPool[val.hashc];
        } else {
            this._UsedPool[val.hashc] = val;
            delete this._IdlePool[val.hashc];
        }
	}
	public addFish(val:IFish):void {
        val.setProtocol(this);
        if(val.isIdle) {
            this._IdlePool[val.hashc] = val;
        } else {
            this._UsedPool[val.hashc] = val;
        }
	}
	public getFish(type:string):IFish  {
        this.count += 1;
        let obj:IFish  = null;
        const keys = Object.keys(this._IdlePool);
        for (let i = 0,len = keys.length; i < len; i += 1) {
            const key = keys[i];
            obj = this._IdlePool[key] as IFish;
            // 如果类型一致,则为可复用对象,返回引用并重置状态
            if (obj.type === type) {
                obj.reset();
                if (this.count % 500 === 0) this.clearRegularly();
                return obj;
            }
        }
        if (this.count % 500 === 0) this.clearRegularly();
        return null;
	}
	public clear():void {
        let obj:IFish = null;
        for (let key in this._IdlePool) {
            obj = this._IdlePool[key] as IFish;
            // 释放与Distributor的链接
            obj.del();
        }
        this._IdlePool = null;
        this._IdlePool = {};
	}
	private clearRegularly() {
        const cur = Date.now();
        const keys = Object.keys(this._IdlePool);
        for (let i = 0,len = keys.length; i < len; i += 1) {
            const key = keys[i];
            const obj = this._IdlePool[key] as IFish;
            // 复用hash中存在10分钟内未复用过的对象进行销毁动作
            if (cur - obj.lastUsed > 600 * 1000) { 
                obj.del();
                delete this._IdlePool[key];
            }
        }
	}
    ...
    //more code
    ...
}
最终效果

游戏运行期间,鱼的总数量维持在80-114之间,内存稳定在27-31mb之间,游戏效果总体流畅度得到相应的提升,用户的游戏体验得到改善。目前游戏整体效果得到一定的提升,但仍存在性能优化的空间,后续会继续提升游戏的性能,给用户更好的体验。
效果1
效果2
效果3
效果4

欢迎大家留言讨论,祝工作顺利、生活愉快!

我是bigo前端,下期见。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第六十二周周报

学习目标&#xff1a; 一、实验 二、论文 学习时间&#xff1a; 2023.11.11-2023.11.17 学习产出&#xff1a; 实验 1、CB模块实验效果出来了&#xff0c;加上去效果不太行&#xff0c;后续实验考虑是否将CB模块换到其他地方 2、CiFAR100实验已完成&#xff0c;效果比Vi…

大模型之十二十-中英双语开源大语言模型选型

从ChatGPT火爆出圈到现在纷纷开源的大语言模型&#xff0c;众多出入门的学习者以及跃跃欲试的公司不得不面临的是开源大语言模型的选型问题。 基于开源商业许可的开源大语言模型可以极大的节省成本和加速业务迭代。 当前&#xff08;2023年11月17日)开源的大语言模型如下&#…

基于DE10-Standard Cyclone V SoC FPGA学习---开发板简介

基于DE10-Standard Cyclone V SoC FPGA学习---开发板简介 简介产品规格基于 ARM 的 HPS配置与调试存储器件通讯连接头显示器音频视频输入模数转换器开关、按钮、指示器传感器电源 DE10-Standard 开发板系统框图Connect HTG 组件配置设计资源其他资源 简介 开发板资料 见 DE10-…

什么是CRM管理系统

什么是CRM管理系统 市场竞争的日益激烈&#xff0c;企业对于客户关系的重视程度不断提升。为了更好地管理和维护客户关系&#xff0c;很多企业开始引入CRM&#xff08;Customer Relationship Management&#xff09;管理系统。那么&#xff0c;什么是CRM管理系统呢&#xff1f…

Jenkins代码检测和本地静态检查

1&#xff1a;Jenkins简介 Jenkins是一个用Java编写的开源的持续集成工具&#xff1b;Jenkins自动化部署可以解决集成、测试、部署等重复性的工作&#xff0c;工具集成的效率明显高于人工操作&#xff1b;并且持续集成可以更早的获取代码变更的信息&#xff0c;从而更早的进入测…

Java 之拼图小游戏

声明 此项目为java基础的阶段项目,此项目涉及了基础语法,面向对象等知识,具体像语法基础如判断,循环,数组,字符串,集合等…; 面向对象如封装,继承,多态,抽象类,接口,内部类等等…都有涉及。此项目涉及的内容比较多,作为初学者可以很好的将前面的知识串起来。此项目拿来练手以及…

golang学习笔记——基础01

文章目录 golang概述Go 语言特色Go 语言用途 Go 语言结构执行 Go 程序 Go 语言包管理01Go 语言包管理02Go 语言基础语法Go 标记行分隔符注释标识符字符串连接关键字、预定义标识符Go 语言的空格格式化字符串 Go 语言数据类型数字类型浮点型其他数字类型 Go 语言变量变量声明零值…

Linux下安装部署redis(离线模式)

一、准备工作 1.下载redis的安装包 下载地址&#xff1a;Index of /releases/ 大家可以自行选择redis的版本&#xff0c;笔者选择的是最新的 2.上传到服务器 前提是我先在服务器上创建了一个目录redis7.2.3&#xff0c;我直接上传到这个目录下 二、安装redis 1.解压redis t…

03-瑞吉外卖关于菜品/套餐分类表的增删改查

新增菜品/套餐分类 页面原型 当我们在后台系统中添加菜品/套餐时,需要选择一个菜品/套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐 第一步: 用户点击确定按钮执行submitForm函数发送Ajax请求,将新增菜品/套餐表单中输入的数据以json形式提交给服务端,…

代码随想录算法训练营第24天|77. 组合

JAVA代码编写 77. 组合 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ]示例 2&#xff1a; 输入…

IIC协议保姆级教学

目录 1.IIC协议概述 2.IIC总线传输 3.IIC-51单片机应用 1.起始信号 2.终止信号 3.应答信号 4.数据发送 4.IIC-32单片机应用 用到的库函数&#xff1a; 1.IIC协议概述 IIC全称Inter-Integrated Circuit (集成电路总线)是由PHILIPS公司在80年代开发的两线式串行总线&am…

hive sql 取当周周一 str_to_date(DATE_FORMAT(biz_date, ‘%Y%v‘), ‘%Y%v‘)

select str_to_date(DATE_FORMAT(biz_date, %Y%v), %Y%v)方法拆解 select DATE_FORMAT(now(), %Y%v), str_to_date(202346, %Y%v)

和鲸科技创始人范向伟受邀出席“凌云出海,来中东吧”2023华为云上海路演活动

11月9日&#xff0c;华为云“凌云出海&#xff0c;来中东吧”系列路演活动第二场在上海正式开启。聚焦“创业全球化”&#xff0c;本次活动由华为云携手阿布扎比投资办公室&#xff08;ADIO&#xff09;举办&#xff0c;旨在与渴望出海发展的优秀创业者们共探出海中东新商机。 …

qt 重载信号,使用““方式进行connect()调用解决方案

问题 在Qt中&#xff0c;重载的信号默认是无法使用&这种方式调用的。 因为&只能绑定到一个具体的信号&#xff0c;而重载的信号名称相同&#xff0c;编译器无法确定要绑定哪一个信号。 解决方案 如果非要使用&绑定重载的信号&#xff0c;可以使用函数指针进行转…

元宇宙3D云展厅应用到汽车销售的方案及特点

为了紧紧抓住年轻消费者的需求&#xff0c;汽车销售行业也正在经历一场深刻的变革。在这个变革的前沿&#xff0c;元宇宙3D汽车展厅作为一项全新技术闪亮登场&#xff0c;打破了传统汽车销售模式的限制&#xff0c;为消费者带来了前所未有的购车体验。 元宇宙3D汽车展厅采用了尖…

基于java web的中小型人力资源管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

音频类型转换工具-可执行文件exe/dmg制作

朋友车载音乐需要MP3格式&#xff0c;想要个批量转换工具 准备工作 brew install ffmpeg --HEAD或者官网下载安装ffmpeg并配置环境conda install ffmpeg 或者pip install ffmpeg-python 音频类型转换程序.py文件 exe文件在windows下打包&#xff0c;dmg在macos下打包&#…

2023年咸阳市《网络建设与运维》赛题解析------四、安全配置

安全配置 说明:IP地址按照题目给定的顺序用“ip/mask”表示,IPv4 any地址用0.0.0.0/0,IPv6 any地址用::/0,禁止用地址条目,否则按零分处理。 1.FW1配置IPv4 nat,实现集团产品1段IPv4访问Internet IPv4,转换ip/mask为200.200.200.16/28,保证每一个源IP产生的所有会话将…

用 js 实现数组中指定元素的替换

文章目录 题目分析 题目 已知 数组 arr [160, 20, 179, 10, -170, -20]&#xff1b;请将数组 arr 中的 [179, 10] 替换为 [-178.16883, 13.27614] 分析 const arr [160, 20, 179, 10, -170, -20]; const replaceArr [179, 10]; const replacement [-178.16883, 13.27614…

[工业自动化-23]:西门子S7-15xxx编程 - 软件编程 - 西门子PLC人机界面交互HMI功能概述、硬件环境准备、软件环境准备

目录 一、什么是人机界面 二、什么是PLC人机交互界面HMI 三、人机界面设计的功能列表 四、开发主机与PLC的连接方式 五、开发主机与HMI的连接方式 六、HMI组态 一、什么是人机界面 人机界面是指人与机器或系统之间的交互界面。它是人类与计算机或其他设备之间进行信息交换…