【分享】【优雅的|可扩展|简单的】加载进度处理方式

在项目中我发现有的同学进度加载比较混乱,想到什么就加什么,后期进度显示要么停住,要么乱了。因此今天随手写了个进度管理器分享给大家(Github):
任务管理器:

export type Progressor = (p: number) => void;
export type Runner = (task: Task, progress: Progressor) => Promise<boolean>;

export class Task {
    name: string;
    weight: number;
    runner: Runner;

    constructor(name: string, weight: number, runner: Runner) {
        this.name = name;
        this.weight = weight;
        this.runner = runner;
    }
}

export class TaskManager {
    private _tasks: Task[] = [];
    private _totalWeight: number = 0;

    add(name: string, weight: number, runnder: Runner) {
        this._tasks.push(new Task(name, weight, runnder));
        this._totalWeight += weight;
    }

    async runSerial(progress: Progressor, thisObj: any) {      
        var weight = 0;   
        let totalTime = Date.now();
        progress?.call(thisObj, 0);        
        for (let task of this._tasks) {
            console.log(`begin task ${task.name}`);
            let dt = Date.now();
            let ret = await task.runner(task, (p) => {
                let w = weight + task.weight * p;
                let pp = w / this._totalWeight;
                progress?.call(thisObj, pp);

                // console.log(`task ${task.name} progress ${pp}`);
            });
            
            if (!ret) {
                return false;
            }

            weight += task.weight;
            let pp = weight / this._totalWeight;
            progress?.call(thisObj, pp);

            console.log(`task ${task.name} done, cost ${Date.now() - dt}ms`);
        }
        console.log(`total cost ${Date.now() - totalTime}ms`);
        return true;
    }

    async runParallel(progress: Progressor, thisObj: any) {  
        var weight = 0; 
        let tasks = this._tasks.map(task => {
            return new Promise(async (resolve, reject) => {
                console.log(`begin task ${task.name}`);
                let dt = Date.now();
                let ret = await task.runner(task, (p) => {
                    let w = weight + task.weight * p;
                    let pp = w / this._totalWeight;
                    progress?.call(thisObj, pp);

                    // console.log(`task ${task.name} progress ${pp}`);
                });
                weight += task.weight;
                console.log(`task ${task.name} done, cost ${Date.now() - dt}ms`);              
                let pp = weight / this._totalWeight;
                progress?.call(thisObj, pp);

                resolve(ret);
            });
        });

        let totalTime = Date.now();
        let ret = await Promise.all(tasks);
        console.log(`total cost ${Date.now() - totalTime}ms`);

        if (ret.indexOf(false) >= 0) {
            return false;
        }
        return true;
    }
}

使用方式:

async loading() {
        let loadMgr = new TaskManager();
        loadMgr.add("加载资源包", 1, async (t, p) => {
            await ResManager.loadResBundle();
            return true;
        });

        loadMgr.add("加载配置表", 3, async (t, p) => {
            await ResManager.loadDirRes("data", (p0) => {
                p(p0 * 0.008);
            });
            await ConfigManager.I.loadConfig();
            
            return true;
        });

        loadMgr.add("登录", 1, async (t, p) => {
            await this.login();          
            return true;
        });

        loadMgr.add("UI预加载", 3, async (t, p) => {
            await ResManager.loadABDirRes("ui", (p0) => {
                p(p0 * 0.007);
            });
            return true;
        });

        loadMgr.add("加载特效", 1, async (t, p) => {
            await ResManager.loadDirRes("anim-effects", (p0) => {
                p(p0 * 0.001);
            });          
            return true;
        });

        loadMgr.add("加载音效", 1, async (t, p) => {
            await ResManager.loadDirRes("audio", (p0) => {
                p(p0 * 0.001);
            });          
            return true;
        });

        loadMgr.add("基础设置", 1, async (t, p) => {
            return true;
        });

        loadMgr.add("加载场景", 10, async (t, p) => {
            let ab = ResManager.getResBundle();
            let sceneName = "game";
            return await new Promise(resolve => {
                ab.preloadScene(sceneName, null, 
                    (finish, total, item) => {
                        if(total < 10) {
                            p(0);
                        }else{
                            let cur = finish / total;
                            p(cur);
                        }
                    }, () => {
                        director.loadScene(sceneName);
                        resolve(true);
                    });
            });
        });

        loadMgr.runSerial((p) => {
            this.progress = p;
        }, this);
    }

可以看到,每个进度只需要关心当前任务在整个任务中所占的权重以及,当前任务自身的进度就行了。

19赞

make 3.x优雅的加载进度

mark一下

mark 优雅

“随手写了个”
牛B!

马克 优雅永不过时

mark mark mark

请问下 “p(p0 * 0.007); ” 的作用是什么?

回传当前进度,因为p0的值是0-100所以需要乘0.01,然后此时我只想返回0.7以内(后面还有个任务,我删了),所以是p00.010.7

对于进度条的信息显示内容怎么处理呢
我理解的进度加载应该有两部分,
一个是游戏资源,一个是非游戏资源的部分,比如网络连接。两者是promise.all的关系。
而对于游戏资源,是顺序执行的关系,这样方便进度条的信息显示。

可以自己组合。顺序和并行都支持

懂了,就是await ResManager.loadABDirRes(“ui”, (p0) => {
p(p0 * 0.007);
});后面传的方法,是用来一直更新进度条的对吧。

对。传此子任务的进度就行了。

有个缺点,假设我现在有个任务队列

任务队列分为 子任务1 子任务2 子任务3
子任务1含有 逻辑1 逻辑2 逻辑3
子任务2含有 逻辑4 逻辑5
子任务3含有 逻辑6 逻辑7

现在我想让 子任务 1-2-3并列执行, 然后全部q

有个缺点,假设我现在有个任务队列

任务队列分为 子任务1 子任务2 子任务3
子任务1含有 逻辑1 逻辑2 逻辑3
子任务2含有 逻辑4 逻辑5
子任务3含有 逻辑6 逻辑7

现在我想让 子任务 1-2-3并列执行, 然后全部全部完成之后再通知我完成

再仔细看看。可以做到的

感谢大佬的任务管理器 :smile:

大佬,git链接打不开,可以重新弄下吗?

那边只是一个代码片段, 帖子里面的就是git的全部代码。

大佬 这个ResManager 这个在哪里定义的啊