mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Improved DisposablePromise disposal process.
This commit is contained in:
parent
79a68a6b7b
commit
603c221365
@ -1,79 +1,214 @@
|
||||
import { Disposable } from "./observable/Disposable";
|
||||
|
||||
export class DisposablePromise<T> extends Promise<T> implements Disposable {
|
||||
static resolve<T>(value?: T | PromiseLike<T>): DisposablePromise<T> {
|
||||
return new DisposablePromise((resolve, reject) => {
|
||||
if (value === undefined) {
|
||||
new DisposablePromise(() => undefined);
|
||||
} else if ("then" in value) {
|
||||
value.then(resolve, reject);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
enum State {
|
||||
Pending,
|
||||
Fulfilled,
|
||||
Rejected,
|
||||
Disposed,
|
||||
}
|
||||
|
||||
static wrap<T>(promise: Promise<T>, dispose?: () => void): DisposablePromise<T> {
|
||||
if (promise instanceof DisposablePromise) {
|
||||
return promise;
|
||||
} else {
|
||||
return new DisposablePromise((resolve, reject) => {
|
||||
promise.then(resolve).catch(reject);
|
||||
}, dispose);
|
||||
}
|
||||
}
|
||||
export class DisposablePromise<T> implements Promise<T>, Disposable {
|
||||
static all<T>(values: Iterable<T | PromiseLike<T>>): DisposablePromise<T[]> {
|
||||
return new DisposablePromise(
|
||||
(resolve, reject) => {
|
||||
const results: T[] = [];
|
||||
let len = 0;
|
||||
|
||||
private disposed: boolean;
|
||||
function add_result(r: T): void {
|
||||
results.push(r);
|
||||
|
||||
private readonly disposal_handler?: () => void;
|
||||
if (results.length === len) {
|
||||
resolve(results);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
executor: (
|
||||
resolve: (value?: T | PromiseLike<T>) => void,
|
||||
reject: (reason?: any) => void,
|
||||
) => void,
|
||||
dispose?: () => void,
|
||||
) {
|
||||
let resolve_fn: (value?: T | PromiseLike<T> | undefined) => void;
|
||||
let reject_fn: (value?: T | PromiseLike<T> | undefined) => void;
|
||||
for (const value of values) {
|
||||
len++;
|
||||
|
||||
super((resolve, reject) => {
|
||||
resolve_fn = resolve;
|
||||
reject_fn = reject;
|
||||
});
|
||||
|
||||
this.disposed = false;
|
||||
this.disposal_handler = dispose;
|
||||
|
||||
executor(
|
||||
value => {
|
||||
if (!this.disposed) {
|
||||
resolve_fn(value);
|
||||
if (is_promise_like(value)) {
|
||||
value.then(add_result, reject);
|
||||
} else {
|
||||
add_result(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
reason => {
|
||||
if (!this.disposed) {
|
||||
reject_fn(reason);
|
||||
() => {
|
||||
for (const value of values) {
|
||||
if (value instanceof DisposablePromise) {
|
||||
value.dispose();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static resolve<T>(value: T | PromiseLike<T>, dispose?: () => void): DisposablePromise<T> {
|
||||
if (is_promise_like(value)) {
|
||||
return new DisposablePromise((resolve, reject) => {
|
||||
value.then(resolve, reject);
|
||||
}, dispose);
|
||||
} else {
|
||||
return new DisposablePromise(resolve => {
|
||||
resolve(value);
|
||||
}, dispose);
|
||||
}
|
||||
}
|
||||
|
||||
private state: State = State.Pending;
|
||||
private value?: T;
|
||||
private reason?: any;
|
||||
|
||||
private readonly fulfillment_listeners: ((value: T) => unknown)[] = [];
|
||||
private readonly rejection_listeners: ((reason: any) => unknown)[] = [];
|
||||
private readonly disposal_handler?: () => void;
|
||||
|
||||
[Symbol.toStringTag] = "DisposablePromise";
|
||||
|
||||
constructor(
|
||||
executor: (
|
||||
resolve: (value: T | PromiseLike<T>) => void,
|
||||
reject: (reason?: any) => void,
|
||||
) => void,
|
||||
dispose?: () => void,
|
||||
) {
|
||||
this.disposal_handler = dispose;
|
||||
|
||||
executor(this.executor_resolve, this.executor_reject);
|
||||
}
|
||||
|
||||
private executor_resolve = (value: T | PromiseLike<T>): void => {
|
||||
if (is_promise_like(value)) {
|
||||
if (this.state !== State.Pending) return;
|
||||
|
||||
value.then(
|
||||
p_value => {
|
||||
this.fulfilled(p_value);
|
||||
},
|
||||
p_reason => {
|
||||
this.rejected(p_reason);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
this.fulfilled(value);
|
||||
}
|
||||
};
|
||||
|
||||
private executor_reject = (reason?: any): void => {
|
||||
this.rejected(reason);
|
||||
};
|
||||
|
||||
private fulfilled(value: T): void {
|
||||
if (this.state !== State.Pending) return;
|
||||
|
||||
this.state = State.Fulfilled;
|
||||
this.value = value;
|
||||
|
||||
for (const listener of this.fulfillment_listeners) {
|
||||
listener(value);
|
||||
}
|
||||
|
||||
this.fulfillment_listeners.splice(0);
|
||||
this.rejection_listeners.splice(0);
|
||||
}
|
||||
|
||||
private rejected(reason?: any): void {
|
||||
if (this.state !== State.Pending) return;
|
||||
|
||||
this.state = State.Rejected;
|
||||
this.reason = reason;
|
||||
|
||||
for (const listener of this.rejection_listeners) {
|
||||
listener(reason);
|
||||
}
|
||||
|
||||
this.fulfillment_listeners.splice(0);
|
||||
this.rejection_listeners.splice(0);
|
||||
}
|
||||
|
||||
then<TResult1 = T, TResult2 = never>(
|
||||
onfulfilled?: ((value: T) => PromiseLike<TResult1> | TResult1) | undefined | null,
|
||||
onrejected?: ((reason: any) => PromiseLike<TResult2> | TResult2) | undefined | null,
|
||||
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
|
||||
): DisposablePromise<TResult1 | TResult2> {
|
||||
return DisposablePromise.wrap(super.then(onfulfilled, onrejected), () => this.dispose());
|
||||
return new DisposablePromise(
|
||||
(resolve, reject) => {
|
||||
if (onfulfilled == undefined) {
|
||||
this.add_fulfillment_listener(resolve as any);
|
||||
} else {
|
||||
this.add_fulfillment_listener(value => {
|
||||
try {
|
||||
resolve(onfulfilled(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (onrejected == undefined) {
|
||||
this.add_rejection_listener(reject);
|
||||
} else {
|
||||
this.add_rejection_listener(reason => {
|
||||
try {
|
||||
resolve(onrejected(reason));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
() => this.dispose(),
|
||||
);
|
||||
}
|
||||
|
||||
catch<TResult = never>(
|
||||
onrejected?: ((reason: any) => PromiseLike<TResult> | TResult) | undefined | null,
|
||||
): DisposablePromise<T | TResult> {
|
||||
return DisposablePromise.wrap(super.catch(onrejected), () => this.dispose());
|
||||
return new DisposablePromise(
|
||||
(resolve, reject) => {
|
||||
this.add_fulfillment_listener(resolve as any);
|
||||
|
||||
if (onrejected == undefined) {
|
||||
this.add_rejection_listener(reject);
|
||||
} else {
|
||||
this.add_rejection_listener(reason => {
|
||||
try {
|
||||
resolve(onrejected(reason));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
() => this.dispose(),
|
||||
);
|
||||
}
|
||||
|
||||
finally(onfinally?: (() => void) | undefined | null): DisposablePromise<T> {
|
||||
return DisposablePromise.wrap(super.finally(onfinally), () => this.dispose());
|
||||
if (onfinally == undefined) {
|
||||
return this;
|
||||
} else {
|
||||
return new DisposablePromise(
|
||||
(resolve, reject) => {
|
||||
this.add_fulfillment_listener(value => {
|
||||
try {
|
||||
onfinally();
|
||||
resolve(value);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
this.add_rejection_listener(value => {
|
||||
try {
|
||||
onfinally();
|
||||
reject(value);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
() => this.dispose(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,9 +216,41 @@ export class DisposablePromise<T> extends Promise<T> implements Disposable {
|
||||
* be called.
|
||||
*/
|
||||
dispose(): void {
|
||||
if (!this.disposed) {
|
||||
this.disposed = true;
|
||||
if (this.state !== State.Disposed) {
|
||||
this.state = State.Disposed;
|
||||
this.disposal_handler?.();
|
||||
}
|
||||
}
|
||||
|
||||
private add_fulfillment_listener(listener: (value: T) => unknown): void {
|
||||
switch (this.state) {
|
||||
case State.Pending:
|
||||
this.fulfillment_listeners.push(listener);
|
||||
break;
|
||||
case State.Fulfilled:
|
||||
listener(this.value!);
|
||||
break;
|
||||
case State.Rejected:
|
||||
case State.Disposed:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private add_rejection_listener(listener: (reason: any) => unknown): void {
|
||||
switch (this.state) {
|
||||
case State.Pending:
|
||||
this.rejection_listeners.push(listener);
|
||||
break;
|
||||
case State.Rejected:
|
||||
listener(this.reason);
|
||||
break;
|
||||
case State.Fulfilled:
|
||||
case State.Disposed:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function is_promise_like<T>(value?: T | PromiseLike<T>): value is PromiseLike<T> {
|
||||
return value != undefined && typeof (value as any).then === "function";
|
||||
}
|
||||
|
@ -26,15 +26,11 @@ DEFAULT_ENTITY.translate(0, 10, 0);
|
||||
DEFAULT_ENTITY.computeBoundingBox();
|
||||
DEFAULT_ENTITY.computeBoundingSphere();
|
||||
|
||||
const DEFAULT_ENTITY_PROMISE: DisposablePromise<BufferGeometry> = DisposablePromise.resolve(
|
||||
DEFAULT_ENTITY,
|
||||
);
|
||||
const DEFAULT_ENTITY_PROMISE = DisposablePromise.resolve<BufferGeometry>(DEFAULT_ENTITY);
|
||||
|
||||
const DEFAULT_ENTITY_TEX: Texture[] = [];
|
||||
|
||||
const DEFAULT_ENTITY_TEX_PROMISE: DisposablePromise<Texture[]> = DisposablePromise.resolve(
|
||||
DEFAULT_ENTITY_TEX,
|
||||
);
|
||||
const DEFAULT_ENTITY_TEX_PROMISE = DisposablePromise.resolve<Texture[]>(DEFAULT_ENTITY_TEX);
|
||||
|
||||
export class EntityAssetLoader implements Disposable {
|
||||
private readonly disposer = new Disposer();
|
||||
@ -91,7 +87,7 @@ export class EntityAssetLoader implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
load_data(
|
||||
private load_data(
|
||||
type: EntityType,
|
||||
asset_type: AssetType,
|
||||
): DisposablePromise<{ url: string; data: ArrayBuffer }> {
|
||||
@ -191,7 +187,14 @@ enum AssetType {
|
||||
Texture,
|
||||
}
|
||||
|
||||
function entity_type_to_url(type: EntityType, asset_type: AssetType): string {
|
||||
/**
|
||||
* @param type
|
||||
* @param asset_type
|
||||
* @param no - Asset number. Some entities have multiple assets that need to be combined.
|
||||
*/
|
||||
function entity_type_to_url(type: EntityType, asset_type: AssetType, no?: number): string {
|
||||
const no_str = no == undefined ? "" : `-${no}`;
|
||||
|
||||
if (is_npc_type(type)) {
|
||||
switch (type) {
|
||||
// The dubswitch model is in XJ format.
|
||||
@ -201,53 +204,55 @@ function entity_type_to_url(type: EntityType, asset_type: AssetType): string {
|
||||
// Episode II VR Temple
|
||||
|
||||
case NpcType.Hildebear2:
|
||||
return entity_type_to_url(NpcType.Hildebear, asset_type);
|
||||
return entity_type_to_url(NpcType.Hildebear, asset_type, no);
|
||||
case NpcType.Hildeblue2:
|
||||
return entity_type_to_url(NpcType.Hildeblue, asset_type);
|
||||
return entity_type_to_url(NpcType.Hildeblue, asset_type, no);
|
||||
case NpcType.RagRappy2:
|
||||
return entity_type_to_url(NpcType.RagRappy, asset_type);
|
||||
return entity_type_to_url(NpcType.RagRappy, asset_type, no);
|
||||
case NpcType.Monest2:
|
||||
return entity_type_to_url(NpcType.Monest, asset_type);
|
||||
return entity_type_to_url(NpcType.Monest, asset_type, no);
|
||||
case NpcType.Mothmant2:
|
||||
return entity_type_to_url(NpcType.Mothmant, asset_type);
|
||||
return entity_type_to_url(NpcType.Mothmant, asset_type, no);
|
||||
case NpcType.PoisonLily2:
|
||||
return entity_type_to_url(NpcType.PoisonLily, asset_type);
|
||||
return entity_type_to_url(NpcType.PoisonLily, asset_type, no);
|
||||
case NpcType.NarLily2:
|
||||
return entity_type_to_url(NpcType.NarLily, asset_type);
|
||||
return entity_type_to_url(NpcType.NarLily, asset_type, no);
|
||||
case NpcType.GrassAssassin2:
|
||||
return entity_type_to_url(NpcType.GrassAssassin, asset_type);
|
||||
return entity_type_to_url(NpcType.GrassAssassin, asset_type, no);
|
||||
case NpcType.Dimenian2:
|
||||
return entity_type_to_url(NpcType.Dimenian, asset_type);
|
||||
return entity_type_to_url(NpcType.Dimenian, asset_type, no);
|
||||
case NpcType.LaDimenian2:
|
||||
return entity_type_to_url(NpcType.LaDimenian, asset_type);
|
||||
return entity_type_to_url(NpcType.LaDimenian, asset_type, no);
|
||||
case NpcType.SoDimenian2:
|
||||
return entity_type_to_url(NpcType.SoDimenian, asset_type);
|
||||
return entity_type_to_url(NpcType.SoDimenian, asset_type, no);
|
||||
case NpcType.DarkBelra2:
|
||||
return entity_type_to_url(NpcType.DarkBelra, asset_type);
|
||||
return entity_type_to_url(NpcType.DarkBelra, asset_type, no);
|
||||
|
||||
// Episode II VR Spaceship
|
||||
|
||||
case NpcType.SavageWolf2:
|
||||
return entity_type_to_url(NpcType.SavageWolf, asset_type);
|
||||
return entity_type_to_url(NpcType.SavageWolf, asset_type, no);
|
||||
case NpcType.BarbarousWolf2:
|
||||
return entity_type_to_url(NpcType.BarbarousWolf, asset_type);
|
||||
return entity_type_to_url(NpcType.BarbarousWolf, asset_type, no);
|
||||
case NpcType.PanArms2:
|
||||
return entity_type_to_url(NpcType.PanArms, asset_type);
|
||||
return entity_type_to_url(NpcType.PanArms, asset_type, no);
|
||||
case NpcType.Dubchic2:
|
||||
return entity_type_to_url(NpcType.Dubchic, asset_type);
|
||||
return entity_type_to_url(NpcType.Dubchic, asset_type, no);
|
||||
case NpcType.Gilchic2:
|
||||
return entity_type_to_url(NpcType.Gilchic, asset_type);
|
||||
return entity_type_to_url(NpcType.Gilchic, asset_type, no);
|
||||
case NpcType.Garanz2:
|
||||
return entity_type_to_url(NpcType.Garanz, asset_type);
|
||||
return entity_type_to_url(NpcType.Garanz, asset_type, no);
|
||||
case NpcType.Dubswitch2:
|
||||
return entity_type_to_url(NpcType.Dubswitch, asset_type);
|
||||
return entity_type_to_url(NpcType.Dubswitch, asset_type, no);
|
||||
case NpcType.Delsaber2:
|
||||
return entity_type_to_url(NpcType.Delsaber, asset_type);
|
||||
return entity_type_to_url(NpcType.Delsaber, asset_type, no);
|
||||
case NpcType.ChaosSorcerer2:
|
||||
return entity_type_to_url(NpcType.ChaosSorcerer, asset_type);
|
||||
return entity_type_to_url(NpcType.ChaosSorcerer, asset_type, no);
|
||||
|
||||
default:
|
||||
return `/npcs/${NpcType[type]}.${asset_type === AssetType.Geometry ? "nj" : "xvm"}`;
|
||||
return `/npcs/${NpcType[type]}${no_str}.${
|
||||
asset_type === AssetType.Geometry ? "nj" : "xvm"
|
||||
}`;
|
||||
}
|
||||
} else {
|
||||
if (asset_type === AssetType.Geometry) {
|
||||
@ -268,13 +273,13 @@ function entity_type_to_url(type: EntityType, asset_type: AssetType): string {
|
||||
case ObjectType.FallingRock:
|
||||
case ObjectType.DesertFixedTypeBoxBreakableCrystals:
|
||||
case ObjectType.BeeHive:
|
||||
return `/objects/${object_data(type).pso_id}.nj`;
|
||||
return `/objects/${object_data(type).pso_id}${no_str}.nj`;
|
||||
|
||||
default:
|
||||
return `/objects/${object_data(type).pso_id}.xj`;
|
||||
return `/objects/${object_data(type).pso_id}${no_str}.xj`;
|
||||
}
|
||||
} else {
|
||||
return `/objects/${object_data(type).pso_id}.xvm`;
|
||||
return `/objects/${object_data(type).pso_id}${no_str}.xvm`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,13 @@ export class FileSystemHttpClient implements HttpClient {
|
||||
get(url: string): HttpResponse {
|
||||
return {
|
||||
json<T>(): DisposablePromise<T> {
|
||||
return DisposablePromise.wrap(fs.promises.readFile(`./assets${url}`)).then(buf =>
|
||||
return DisposablePromise.resolve(fs.promises.readFile(`./assets${url}`)).then(buf =>
|
||||
JSON.parse(buf.toString()),
|
||||
);
|
||||
},
|
||||
|
||||
array_buffer(): DisposablePromise<ArrayBuffer> {
|
||||
return DisposablePromise.wrap(fs.promises.readFile(`./assets${url}`)).then(buf =>
|
||||
return DisposablePromise.resolve(fs.promises.readFile(`./assets${url}`)).then(buf =>
|
||||
buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength),
|
||||
);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user