猫史档案馆


【氵()】论订阅实体的next事件后实体销毁或实体对应的玩家退出后报错(nextDestroy除外)

用户:七式草莓七式草莓查看:0 回复:5 评论:0 创建时间:2022-08-17T13:06:31


以下是代码(这个代码在其他教程中有):

world.onFluidEnter(({ entity }) => {
    if (!entity.isPlayer) return;
    var wearable = entity.player.addWearable({
        bodyPart: Box3BodyPart.HEAD,
        mesh: 'mesh/潜水镜.vb',
        orientation: new Box3Quaternion(0, 1, 0, 0),
        scale: new Box3Vector3(1, 1, 1),
        offset: new Box3Vector3(0, 0, 0.5),
    });
    entity.nextFluidLeave().then(() => wearable.remove())
});

如果在next事件触发前玩家退出地图,会返回一个reject的Promise(内容:Error: entity destroyed),而then中没有处理reject的函数,所以会报错

下面我说的是触发reject的原因,其他的不说

首先,这个事件处理器是ScriptDispatcher的两个方法(on事件对应ScriptDispatcher.prototype.channel,next事件对应ScriptDispatcher.prototype.future)

下面是ScriptDispatcher的源代码:

class ScriptDispatcher {
    constructor(oneShot) {
        this.handlers = [];
        this.promise = [];
        this.destroyed = false;
        this.oneShot = false;
        exports.DISPATCHER_CREATE_COUNT++;
        this.oneShot = !!oneShot;
        this.channel = (handler) => {
            const record = {
                finished: false,
                inQueue: true,
                handler,
            };
            if (!this.destroyed) {
                exports.DISPATCHER_HANDLER_COUNT++;
                this.handlers.push(record);
            }
            return new api_1.Box3EventHandlerToken(() => {
                record.finished = true;
            }, () => {
                if (this.destroyed) {
                    return;
                }
                record.finished = false;
                if (!record.inQueue) {
                    record.inQueue = true;
                    this.handlers.push(record);
                }
            }, () => !record.finished);
        };
        this.future = (filter) => {
            if (this.destroyed) {
                return Promise.reject(new Error('dispatcher destroyed'));
            }
            return new Promise((resolve, reject) => {
                if (this.destroyed) {
                    reject(new Error('dispatcher destroyed'));
                    return;
                }
                exports.DISPATCHER_PROMISE_COUNT++;
                this.promise.push({
                    finished: false,
                    filter,
                    resolve,
                    reject,
                });
            });
        };
    }
    _commonDestroy() {
        exports.DISPATCHER_DESTROY_COUNT++;
        this.destroyed = true;
        const handlers = this.handlers;
        for (let i = 0; i < handlers.length; ++i) {
            const h = handlers[i];
            h.finished = true;
            h.inQueue = false;
        }
        exports.DISPATCHER_HANDLER_COUNT -= handlers.length;
        handlers.length = 0;
    }
    destroy(reason, scheduler) 喵onDestroy();
        const promises = this.promise;
        promises.forEach((p) => {
            if (!p.finished) {
                scheduler.scheduleAt(() => {
                    p.reject(new Error(reason));
                    return true;
                }, scheduler.currentMS());
            }
            p.finished = true;
        });
        exports.DISPATCHER_PROMISE_COUNT -= promises.length;
        promises.length = 0;
    }
    notify(ev, handleException) {
        let dispatched = false;
        const handlers = this.handlers;
        for (let i = 0; i < handlers.length; ++i) {
            if (this.destroyed) {
                break;
            }
            const h = handlers[i];
            if (h.finished) {
                continue;
            }
            dispatched = true;
            try {
                h.handler.call(null, ev);
            } catch (e) {
                handleException(e);
            }
        }
        const promises = this.promise;
        for (let i = 0; i < promises.length; ++i) {
            if (this.destroyed) {
                break;
            }
            const p = promises[i];
            const f = p.filter;
            if (f) {
                try {
                    if (!f.call(null, ev)) {
                        continue;
                    }
                } catch (e) {
                    handleException(e);
                }
            }
            p.finished = true;
            dispatched = true;
            try {
                p.resolve.call(null, ev);
            } catch (e) {
                handleException(e);
            }
        }
        let ptr = 0;
        for (let i = 0; i < promises.length; ++i) {
            const p = promises[i];
            if (!p.finished) {
                exports.DISPATCHER_PROMISE_COUNT--;
                promises[ptr++] = p;
            }
        }
        promises.length = ptr;
        ptr = 0;
        for (let i = 0; i < handlers.length; ++i) {
            const h = handlers[i];
            if (h.finished) {
                h.inQueue = false;
                exports.DISPATCHER_HANDLER_COUNT--;
            } else {
                handlers[ptr++] = h;
            }
        }
        handlers.length = ptr;
        if (this.oneShot) {
            this._commonDestroy();
            promises.forEach((p) => {
                if (!p.finished) {
                    Promise.resolve().then(() => {
                        p.reject(new Error('reason'));
                    }).catch((err) => {
                        throw err;
                    });
                    dispatched = true;
                }
                p.finished = true;
            });
            exports.DISPATCHER_PROMISE_COUNT -= promises.length;
            promises.length = 0;
        }
        if (dispatched) {
            exports.DISPATCHER_EVENT_COUNT++;
        }
        return dispatched;
    }
}

为了解释这个问题,我们需要研究ScriptDispatcher.prototype.future这个函数

下面是ScriptDispatcher.prototype.future的源代码(为了防止找不到)

this.future = (filter) => {
    if (this.destroyed) {
        return Promise.reject(new Error('dispatcher destroyed'));
    }
    return new Promise((resolve, reject) => {
        if (this.destroyed) {
            reject(new Error('dispatcher destroyed'));
            return;
        }
        exports.DISPATCHER_PROMISE_COUNT++;
        this.promise.push({
            finished: false,
            filter,
            resolve,
            reject,
        });
    });
};

由这里可以看出来,这个函数返回了一个Promise(使用Promise构造函数需要执行参数里的resolve或reject才能返回),然后把它添加到了一个数组里

我翻了这个类的其他部分代码,发现notify函数有问题

下面是ScriptDispatcher.prototype.notify的源代码(也是为了防止找不到)

notify(ev, handleException) {
    let dispatched = false;
    const handlers = this.handlers;
    for (let i = 0; i < handlers.length; ++i) {
        if (this.destroyed) {
            break;
        }
        const h = handlers[i];
        if (h.finished) {
            continue;
        }
        dispatched = true;
        try {
            h.handler.call(null, ev);
        } catch (e) {
            handleException(e);
        }
    }
    const promises = this.promise;
    for (let i = 0; i < promises.length; ++i) {
        if (this.destroyed) {
            break;
        }
        const p = promises[i];
        const f = p.filter;
        if (f) {
            try {
                if (!f.call(null, ev)) {
                    continue;
                }
            } catch (e) {
                handleException(e);
            }
        }
        p.finished = true;
        dispatched = true;
        try {
            p.resolve.call(null, ev);
        } catch (e) {
            handleException(e);
        }
    }
    let ptr = 0;
    for (let i = 0; i < promises.length; ++i) {
        const p = promises[i];
        if (!p.finished) {
            exports.DISPATCHER_PROMISE_COUNT--;
            promises[ptr++] = p;
        }
    }
    promises.length = ptr;
    ptr = 0;
    for (let i = 0; i < handlers.length; ++i) {
        const h = handlers[i];
        if (h.finished) {
            h.inQueue = false;
            exports.DISPATCHER_HANDLER_COUNT--;
        } else {
            handlers[ptr++] = h;
        }
    }
    handlers.length = ptr;
    if (this.oneShot) 喵onDestroy();
        promises.forEach((p) => {
            if (!p.finished) {
                Promise.resolve().then(() => {
                    p.reject(new Error('reason'));
                }).catch((err) => {
                    throw err;
                });
                dispatched = true;
            }
            p.finished = true;
        });
        exports.DISPATCHER_PROMISE_COUNT -= promises.length;
        promises.length = 0;
    }
    if (dispatched) {
        exports.DISPATCHER_EVENT_COUNT++;
    }
    return dispatched;
}

这个函数是的上半部分是用来触发ScriptDispatcher.prototype.channel事件的

所以我们要看下半部分(下半部分太复杂了,我给你们解释一下)

1.查找未触发的next事件的Promise

2.触发next事件中传给它的方法(就是nextXXX(() => ...)中的() => ...),如果返回false则等待下一次触发(如果没有这个方法,默认返回true)

3.执行Promise中的resolve函数,把对应的Event返回

上面的就是触发的逻辑,还有oneShot为false,所以不用看了

不过失败是怎么回事,这是destroy方法的功劳

下面是destroy方法的源代码(里面的this._commonDestroy不用看,那个和这个问题没有关系):

destroy(reason, scheduler) {
    this._commonDestroy();
    const promises = this.promise;
    promises.forEach((p) => {
        if (!p.finished) {
            scheduler.scheduleAt(() => {
                p.reject(new Error(reason));
                return true;
            }, scheduler.currentMS());
        }
        p.finished = true;
    });
    exports.DISPATCHER_PROMISE_COUNT -= promises.length;
    promises.length = 0;
}

可见,如果有未触发的Promise事件,就返回reject(返回的值是错误类的实例)

这就是原因,那么如何解决呢

1.实例的代码(就是使用了.then方法的)

在后面加个处理失败的空函数即可(就是.then(..., () => {}))

world.onFluidEnter(({ entity }) => {
    if (!entity.isPlayer) return;
    var wearable = entity.player.addWearable({
        bodyPart: Box3BodyPart.HEAD,
        mesh: 'mesh/潜水镜.vb',
        orientation: new Box3Quaternion(0, 1, 0, 0),
        scale: new Box3Vector3(1, 1, 1),
        offset: new Box3Vector3(0, 0, 0.5),
    });
    entity.nextFluidLeave().then(() => wearable.remove(), () => {})
});

2.使用await的

使用try/catch即可

world.onFluidEnter(async({ entity }) => {
    if (!entity.isPlayer) return;
    var wearable = entity.player.addWearable({
        bodyPart: Box3BodyPart.HEAD,
        mesh: 'mesh/潜水镜.vb',
        orientation: new Box3Quaternion(0, 1, 0, 0),
        scale: new Box3Vector3(1, 1, 1),
        offset: new Box3Vector3(0, 0, 0.5),
    });
    try{
        await entity.nextFluidLeave()
        wearable.remove()
    }catch(err){}
});


回复

上一页1 页 / 共 1下一页
星星上的雪花星星上的雪花

沙发emotion_doge

点赞0


评论


Architect_皮卡awaArchitect_皮卡awa

除了沙发全要了emotion_doge

点赞0


评论


MC斩天MC斩天

看不懂,但大受震撼

点赞0


评论


145a145a

nb

点赞0


评论


CenCen

懂了懂了

点赞0


评论