mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18: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";
|
import { Disposable } from "./observable/Disposable";
|
||||||
|
|
||||||
export class DisposablePromise<T> extends Promise<T> implements Disposable {
|
enum State {
|
||||||
static resolve<T>(value?: T | PromiseLike<T>): DisposablePromise<T> {
|
Pending,
|
||||||
return new DisposablePromise((resolve, reject) => {
|
Fulfilled,
|
||||||
if (value === undefined) {
|
Rejected,
|
||||||
new DisposablePromise(() => undefined);
|
Disposed,
|
||||||
} else if ("then" in value) {
|
}
|
||||||
value.then(resolve, reject);
|
|
||||||
} else {
|
|
||||||
resolve(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static wrap<T>(promise: Promise<T>, dispose?: () => void): DisposablePromise<T> {
|
export class DisposablePromise<T> implements Promise<T>, Disposable {
|
||||||
if (promise instanceof DisposablePromise) {
|
static all<T>(values: Iterable<T | PromiseLike<T>>): DisposablePromise<T[]> {
|
||||||
return promise;
|
return new DisposablePromise(
|
||||||
} else {
|
(resolve, reject) => {
|
||||||
return new DisposablePromise((resolve, reject) => {
|
const results: T[] = [];
|
||||||
promise.then(resolve).catch(reject);
|
let len = 0;
|
||||||
}, dispose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private disposed: boolean;
|
function add_result(r: T): void {
|
||||||
|
results.push(r);
|
||||||
|
|
||||||
private readonly disposal_handler?: () => void;
|
if (results.length === len) {
|
||||||
|
resolve(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
for (const value of values) {
|
||||||
executor: (
|
len++;
|
||||||
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;
|
|
||||||
|
|
||||||
super((resolve, reject) => {
|
if (is_promise_like(value)) {
|
||||||
resolve_fn = resolve;
|
value.then(add_result, reject);
|
||||||
reject_fn = reject;
|
} else {
|
||||||
});
|
add_result(value);
|
||||||
|
}
|
||||||
this.disposed = false;
|
|
||||||
this.disposal_handler = dispose;
|
|
||||||
|
|
||||||
executor(
|
|
||||||
value => {
|
|
||||||
if (!this.disposed) {
|
|
||||||
resolve_fn(value);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reason => {
|
() => {
|
||||||
if (!this.disposed) {
|
for (const value of values) {
|
||||||
reject_fn(reason);
|
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>(
|
then<TResult1 = T, TResult2 = never>(
|
||||||
onfulfilled?: ((value: T) => PromiseLike<TResult1> | TResult1) | undefined | null,
|
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||||
onrejected?: ((reason: any) => PromiseLike<TResult2> | TResult2) | undefined | null,
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
|
||||||
): DisposablePromise<TResult1 | TResult2> {
|
): 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>(
|
catch<TResult = never>(
|
||||||
onrejected?: ((reason: any) => PromiseLike<TResult> | TResult) | undefined | null,
|
onrejected?: ((reason: any) => PromiseLike<TResult> | TResult) | undefined | null,
|
||||||
): DisposablePromise<T | TResult> {
|
): 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> {
|
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.
|
* be called.
|
||||||
*/
|
*/
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
if (!this.disposed) {
|
if (this.state !== State.Disposed) {
|
||||||
this.disposed = true;
|
this.state = State.Disposed;
|
||||||
this.disposal_handler?.();
|
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.computeBoundingBox();
|
||||||
DEFAULT_ENTITY.computeBoundingSphere();
|
DEFAULT_ENTITY.computeBoundingSphere();
|
||||||
|
|
||||||
const DEFAULT_ENTITY_PROMISE: DisposablePromise<BufferGeometry> = DisposablePromise.resolve(
|
const DEFAULT_ENTITY_PROMISE = DisposablePromise.resolve<BufferGeometry>(DEFAULT_ENTITY);
|
||||||
DEFAULT_ENTITY,
|
|
||||||
);
|
|
||||||
|
|
||||||
const DEFAULT_ENTITY_TEX: Texture[] = [];
|
const DEFAULT_ENTITY_TEX: Texture[] = [];
|
||||||
|
|
||||||
const DEFAULT_ENTITY_TEX_PROMISE: DisposablePromise<Texture[]> = DisposablePromise.resolve(
|
const DEFAULT_ENTITY_TEX_PROMISE = DisposablePromise.resolve<Texture[]>(DEFAULT_ENTITY_TEX);
|
||||||
DEFAULT_ENTITY_TEX,
|
|
||||||
);
|
|
||||||
|
|
||||||
export class EntityAssetLoader implements Disposable {
|
export class EntityAssetLoader implements Disposable {
|
||||||
private readonly disposer = new Disposer();
|
private readonly disposer = new Disposer();
|
||||||
@ -91,7 +87,7 @@ export class EntityAssetLoader implements Disposable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
load_data(
|
private load_data(
|
||||||
type: EntityType,
|
type: EntityType,
|
||||||
asset_type: AssetType,
|
asset_type: AssetType,
|
||||||
): DisposablePromise<{ url: string; data: ArrayBuffer }> {
|
): DisposablePromise<{ url: string; data: ArrayBuffer }> {
|
||||||
@ -191,7 +187,14 @@ enum AssetType {
|
|||||||
Texture,
|
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)) {
|
if (is_npc_type(type)) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
// The dubswitch model is in XJ format.
|
// 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
|
// Episode II VR Temple
|
||||||
|
|
||||||
case NpcType.Hildebear2:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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
|
// Episode II VR Spaceship
|
||||||
|
|
||||||
case NpcType.SavageWolf2:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
case NpcType.ChaosSorcerer2:
|
||||||
return entity_type_to_url(NpcType.ChaosSorcerer, asset_type);
|
return entity_type_to_url(NpcType.ChaosSorcerer, asset_type, no);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return `/npcs/${NpcType[type]}.${asset_type === AssetType.Geometry ? "nj" : "xvm"}`;
|
return `/npcs/${NpcType[type]}${no_str}.${
|
||||||
|
asset_type === AssetType.Geometry ? "nj" : "xvm"
|
||||||
|
}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (asset_type === AssetType.Geometry) {
|
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.FallingRock:
|
||||||
case ObjectType.DesertFixedTypeBoxBreakableCrystals:
|
case ObjectType.DesertFixedTypeBoxBreakableCrystals:
|
||||||
case ObjectType.BeeHive:
|
case ObjectType.BeeHive:
|
||||||
return `/objects/${object_data(type).pso_id}.nj`;
|
return `/objects/${object_data(type).pso_id}${no_str}.nj`;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return `/objects/${object_data(type).pso_id}.xj`;
|
return `/objects/${object_data(type).pso_id}${no_str}.xj`;
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
get(url: string): HttpResponse {
|
||||||
return {
|
return {
|
||||||
json<T>(): DisposablePromise<T> {
|
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()),
|
JSON.parse(buf.toString()),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
array_buffer(): DisposablePromise<ArrayBuffer> {
|
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),
|
buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user