1. 性能瓶颈分析

通过性能分析工具可以得知

技能列表刷新一次,需要耗时 650 ms 左右

image-20221209142707273

刷新列表时,刷新了 5 个 item,每个 item 的刷新时间平均为 130 ms。

查看单个 item 的刷新,分为 3 部分,getObjectaddChildrenderListItem

image-20221209143222970

其中 getObjectaddChild 是虚拟列表 GList 添加 item 时必要的步骤, renderListItem 是自己写的渲染 item 的函数。

而在 renderListItem 中,可以发现,updateText 函数占用了大量的时间,达到了总耗时的 93.7%

image-20221209144300825

所以优化 updateText 函数成了技能系统优化的重点。

2. 优化方法

查阅底层代码可以知道,updateText 函数是富文本组件 fgui.GRichTextField 在渲染时候调用的。

所以优化的重点变成了 富文本组件的渲染问题。

2.1 使用普通文本替代富文本

富文本在底层是调用了多个普通文本拼接而成,尤其是使用了 UBB模板 的情况下,渲染非常耗性能。

所以如非必要,可以改为使用普通文本替代。

let label: fgui.GRichTextField = this.getchild("label")?.asRichTextField;
label.text = "恭喜玩家{userName}获得装备{equipName}x{num}"

let userName = "龙傲天";
let equipName = "屠龙刀";
let equipNum = 1;

// 富文本使用模板
label.setVar("userName", userName)
     .setVar("equipName", equipName)
     .setVar("num", `${equipNum}`)
      .flushVars();

使用富文本的模板有很大的好处,就是不需要关注具体的文本内容是什么,只需要关注自己需要设置的内容即可。

是一种很好的 UI逻辑 分离的思路,只可惜刷新显示文本时性能消耗有点多。

可以改成普通文本,这样可以节省很多性能。

let label: fgui.GTextField = this.getchild("label")?.asTextField;

let userName = "龙傲天";
let equipName = "屠龙刀";
let equipNum = 1;

// 普通文本字符串拼接
label.text = `恭喜玩家${userName}获得装备${equipName}x${equipNum}`

2.2 分帧加载

但是并非所有的 RichTextField 都可以使用 TextField 替代。

比如技能描述文本中,需要使用 UBB 语法进行排版,直接将富文本替换成普通文本,显然是无法达到要求的。

也就是说,富文本渲染的消耗是不可避免了。

但是,使用分帧加载的思路,还是有优化的空间的。

一帧内要刷新 5 个富文本组件,会很卡,但是如果每帧只刷新一个富文本组件,就会流畅很多了。

一帧的任务分摊到 5 帧执行完成,时间上相差并不大,但是每帧的任务压力都减轻了很多。

示例代码如下

定义一个渲染任务队列管理类 renderTaskQueue ,用来分帧处理比较耗时的渲染任务。

class renderTaskQueue {
    // 单例模式
    private static ins: renderTaskQueue;
    static instance(): renderTaskQueue {
        if (!this.ins) {
            this.ins = new renderTaskQueue();
        }
        return this.ins;
    }
    
    // 渲染任务队列
    private m_queue: Array<Function> = [];
    
    // 添加一个任务到队列里
    addToQueue(func: Function) {
        if (!func || this.m_queue.indexOf(func) < 0) {
            this.m_queue.push(func);
        }
    }
    
    // 从队列中取一个任务并执行
    executeOne() {
        if (this.m_queue.length > 0) {
            let func = this.m_queue.shift();
            func && func();
        }
    }
    
    // 执行队列中所有任务
    executeAll() {
        this.m_queue.forEach(func => {
            func && func();
        });
        this.m_queue = [];
    }
}

将渲染耗时较长,时效性要求又不是特别高的任务拆分出来,放入渲染队列中执行

// 修改前的 refreshWnd 函数,富文本渲染和图片加载耗时较长
refreshWnd() {
    if (this.textField) {
        this.textField.text = "这个是普通文本";
    }
    
    if (this.richTextField) {
        this.richTextField.text = "这个是[color=#d18888]富文本[/color]";
    }
    
    if (this.loader) {
        this.loader.url = "ui://commonPackage/testImage";
    }
}

// 优化后的 refreshWnd 函数,将耗时较长的任务拆分出来,放入渲染队列中

setRichTextField() {
    if (this.richTextField) {
        this.richTextField.text = "这个是[color=#d18888]富文本[/color]";
    }
}

setLoader() {
    if (this.loader) {
        this.loader.url = "ui://commonPackage/testImage";
    }
}

refreshWnd() {
    if (this.textField) {
        this.textField.text = "这个是普通文本";
    }
    
    renderTaskQueue.instance().addToQueue(this.setRichTextField.bind(this));
    renderTaskQueue.instance().addToQueue(this.setLoader.bind(this));
}

OnUpdate() 中调用 renderTaskQueue 的方法,每帧执行一个任务

OnUpdate() {
    // 每帧执行一次 executeOne() 方法,处理一个任务
    renderTaskQueue.instance().executeOne();
}

3. 优化效果

优化后,使用性能分析工具检查可以知道,任务被拆分到多帧执行了。

image-20221213112137052

技能列表刷新时间,由原先的 650 ms 左右,优化到 290 ms 左右,节省了 55% 的时间。

image-20221213111747074

最后修改:2022 年 12 月 16 日 10 : 13 AM
如果觉得我的文章对你有用,请随意赞赏