Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New singleton implementation that removes all statics #1

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 20 additions & 52 deletions packages/dev/core/src/Meshes/Compression/dracoCodec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Nullable } from "core/types";
import { Tools } from "../../Misc/tools";
import { AutoReleaseWorkerPool } from "../../Misc/workerPool";
import type { IDisposable } from "../../scene";
import { initializeWebWorker } from "./dracoCompressionWorker";
import { Logger } from "core/Misc";

/**
* Configuration for using a Draco codec.
Expand Down Expand Up @@ -68,39 +68,9 @@ export abstract class DracoCodec<M> implements IDisposable {
protected _modulePromise?: Promise<{ module: M }>;

/**
* The configuration for the Draco codec.
* Subclasses should override this value with a valid configuration.
* The default configuration for the codec.
*/
public static Config: IDracoCodecConfiguration;

/**
* Returns true if the codec's `Configuration` is available.
*/
public static get Available(): boolean {
return !!((this.Config.wasmUrl && this.Config.wasmBinaryUrl && typeof WebAssembly === "object") || this.Config.fallbackUrl);
}

/**
* The default draco compression object
* Subclasses should override this value using the narrowed type of the subclass,
* and define a public `get Default()` that returns the narrowed instance.
*/
protected static _Default: Nullable<DracoCodec<unknown>>;

/**
* Reset the default draco compression object to null and disposing the removed default instance.
* Note that if the workerPool is a member of the static Configuration object it is recommended not to run dispose,
* unless the static worker pool is no longer needed.
* @param skipDispose set to true to not dispose the removed default instance
*/
public static ResetDefault(skipDispose?: boolean): void {
if (this._Default) {
if (!skipDispose) {
this._Default.dispose();
}
this._Default = null;
}
}
protected abstract readonly _defaultConfig: IDracoCodecConfiguration;

/**
* Checks if the default codec JS module is in scope.
Expand All @@ -118,11 +88,21 @@ export abstract class DracoCodec<M> implements IDisposable {
protected abstract _getWorkerContent(): string;

/**
* Constructor
* @param _config The configuration for the DracoCodec instance.
* Loads the codec module and worker pool if needed.
* @param _config An optional configuration for this DracoDecoder. Defaults to the following:
* - `numWorkers`: 50% of the available logical processors, capped to 4. If no logical processors are available, defaults to 1.
* - `wasmUrl`: `"https://cdn.babylonjs.com/draco_wasm_wrapper_gltf.js"` (decoder)
* - `wasmBinaryUrl`: `"https://cdn.babylonjs.com/draco_decoder_gltf.wasm"` (decoder)
* - `fallbackUrl`: `"https://cdn.babylonjs.com/draco_decoder_gltf.js"` (decoder)
* @returns A promise that resolves when the decoder is ready (module loaded and/or worker pool initialized)
*/
constructor(_config: IDracoCodecConfiguration) {
const config = { numWorkers: _GetDefaultNumWorkers(), ..._config };
public async initialize(_config?: IDracoCodecConfiguration): Promise<void> {
if (this._workerPoolPromise || this._modulePromise) {
Logger.Warn("Draco codec is already initialized. If a configuration change is needed, call dispose() before re-initializing.");
return;
}

const config = { numWorkers: _GetDefaultNumWorkers(), ...this._defaultConfig, ..._config };
// check if the decoder binary and worker pool was injected
// Note - it is expected that the developer checked if WebWorker, WebAssembly and the URL object are available
if (config.workerPool) {
Expand All @@ -148,7 +128,7 @@ export abstract class DracoCodec<M> implements IDisposable {
url: urlNeeded ? Tools.GetBabylonScriptURL(config.fallbackUrl!) : "",
wasmBinaryPromise: Promise.resolve(undefined),
};
// If using workers, initialize a worker pool with either the wasm or url?
// If using workers, initialize a worker pool with either the wasm or url
if (useWorkers) {
this._workerPoolPromise = codecInfo.wasmBinaryPromise.then((wasmBinary) => {
const workerContent = this._getWorkerContent();
Expand All @@ -159,6 +139,8 @@ export abstract class DracoCodec<M> implements IDisposable {
return initializeWebWorker(worker, wasmBinary, codecInfo.url);
});
});
await this._workerPoolPromise;
return;
} else {
this._modulePromise = codecInfo.wasmBinaryPromise.then(async (wasmBinary) => {
if (this._isModuleAvailable()) {
Expand All @@ -171,20 +153,6 @@ export abstract class DracoCodec<M> implements IDisposable {
}
return this._createModuleAsync(wasmBinary as ArrayBuffer, config.jsModule);
});
}
}

/**
* Returns a promise that resolves when ready. Call this manually to ensure draco compression is ready before use.
* @returns a promise that resolves when ready
*/
public async whenReadyAsync(): Promise<void> {
if (this._workerPoolPromise) {
await this._workerPoolPromise;
return;
}

if (this._modulePromise) {
await this._modulePromise;
return;
}
Expand Down
61 changes: 55 additions & 6 deletions packages/dev/core/src/Meshes/Compression/dracoCompression.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { _GetDefaultNumWorkers } from "./dracoCodec";
import type { IDracoCodecConfiguration } from "./dracoCodec";
import { DracoDecoder } from "./dracoDecoder";
import { DefaultDecoderConfig, DracoDecoderClass } from "./dracoDecoder";
import { VertexBuffer } from "../buffer";
import { VertexData } from "../mesh.vertexData";
import type { Nullable } from "core/types";

/**
* Configuration for Draco compression
Expand Down Expand Up @@ -54,36 +55,84 @@ export interface IDracoCompressionOptions extends Pick<IDracoCodecConfiguration,
*
* @see https://playground.babylonjs.com/#DMZIBD#0
*/
export class DracoCompression extends DracoDecoder {
export class DracoCompression extends DracoDecoderClass {
/**
* The configuration. Defaults to the following urls:
* - wasmUrl: "https://cdn.babylonjs.com/draco_wasm_wrapper_gltf.js"
* - wasmBinaryUrl: "https://cdn.babylonjs.com/draco_decoder_gltf.wasm"
* - fallbackUrl: "https://cdn.babylonjs.com/draco_decoder_gltf.js"
*/
public static Configuration: IDracoCompressionConfiguration = {
decoder: DracoDecoder.Config, // TODO: Remove this reference or update the JSDoc with warning.
decoder: { ...DefaultDecoderConfig },
};

/**
* Returns true if the decoder configuration is available.
*/
public static get DecoderAvailable(): boolean {
return DracoDecoder.Available; // TODO: Remove this reference or update the JSDoc with warning.
const decoder = DracoCompression.Configuration.decoder;
return !!((decoder.wasmUrl && decoder.wasmBinaryUrl && typeof WebAssembly === "object") || decoder.fallbackUrl);
}

/**
* Default number of workers to create when creating the draco compression object.
*/
public static DefaultNumWorkers = _GetDefaultNumWorkers();

protected static _Default: Nullable<DracoCompression>;

/**
* Default instance for the draco compression object.
*/
public static get Default(): DracoCompression {
DracoCompression._Default ??= new DracoCompression();
return DracoCompression._Default;
}

/**
* Reset the default draco compression object to null and disposing the removed default instance.
* Note that if the workerPool is a member of the static Configuration object it is recommended not to run dispose,
* unless the static worker pool is no longer needed.
* @param skipDispose set to true to not dispose the removed default instance
*/
public static ResetDefault(skipDispose?: boolean): void {
if (DracoCompression._Default) {
if (!skipDispose) {
DracoCompression._Default.dispose();
}
DracoCompression._Default = null;
}
}

/**
* Constructor
* @param numWorkersOrConfig The number of workers for async operations or a config object. Specify `0` to disable web workers and run synchronously in the current context.
*/
constructor(numWorkersOrConfig: number | IDracoCompressionOptions = DracoCompression.DefaultNumWorkers) {
const config = typeof numWorkersOrConfig === "number" ? { ...DracoDecoder.Config, numWorkers: numWorkersOrConfig } : { ...DracoDecoder.Config, ...numWorkersOrConfig };
super(config);
super();
// Derive config this way to maintain backwards compatibility with "numWorkers"
const mergedConfig = {
...DracoCompression.Configuration.decoder,
...(typeof numWorkersOrConfig === "number" ? { numWorkers: numWorkersOrConfig } : numWorkersOrConfig),
};
// Explicitly initialize here for backwards compatibility
this.initialize(mergedConfig);
}

/**
* Returns a promise that resolves when ready. Call this manually to ensure draco compression is ready before use.
* @returns a promise that resolves when ready
*/
public async whenReadyAsync(): Promise<void> {
if (this._workerPoolPromise) {
await this._workerPoolPromise;
return;
}

if (this._modulePromise) {
await this._modulePromise;
return;
}
}

/**
Expand Down
102 changes: 46 additions & 56 deletions packages/dev/core/src/Meshes/Compression/dracoDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,55 +20,20 @@ interface MeshData {
totalVertices: number;
}

/** @internal Used for `DracoCompression` */
export const DefaultDecoderConfig: IDracoCodecConfiguration = {
wasmUrl: `${Tools._DefaultCdnUrl}/draco_wasm_wrapper_gltf.js`,
wasmBinaryUrl: `${Tools._DefaultCdnUrl}/draco_decoder_gltf.wasm`,
fallbackUrl: `${Tools._DefaultCdnUrl}/draco_decoder_gltf.js`,
};

/**
* @experimental This class is an experimental version of `DracoCompression` and is subject to change.
*
* Draco compression (https://google.github.io/draco/)
*
* This class wraps the Draco decoder module.
*
* By default, the configuration points to a copy of the Draco decoder files for glTF from the Babylon.js preview cdn https://preview.babylonjs.com/draco_wasm_wrapper_gltf.js.
*
* To update the configuration, use the following code:
* ```javascript
* DracoDecoder.Config = {
* wasmUrl: "<url to the WebAssembly library>",
* wasmBinaryUrl: "<url to the WebAssembly binary>",
* fallbackUrl: "<url to the fallback JavaScript library>",
* };
* ```
*
* Draco has two versions, one for WebAssembly and one for JavaScript. The decoder configuration can be set to only support WebAssembly or only support the JavaScript version.
* Decoding will automatically fallback to the JavaScript version if WebAssembly version is not configured or if WebAssembly is not supported by the browser.
* Use `DracoDecoder.Available` to determine if the decoder configuration is available for the current context.
*
* To decode Draco compressed data, get the default DracoDecoder object and call decodeMeshToGeometryAsync:
* ```javascript
* var geometry = await DracoDecoder.Default.decodeMeshToGeometryAsync(data);
* ```
* It should not be constructed directly. Use `DracoDecoder` instead.
* @internal
*/
export class DracoDecoder extends DracoCodec<DecoderModule> {
protected static override _Default: Nullable<DracoDecoder> = null;
/**
* Default instance for the DracoDecoder.
*/
public static get Default(): DracoDecoder {
DracoDecoder._Default ??= new DracoDecoder();
return DracoDecoder._Default;
}

/**
* Configuration for the DracoDecoder. Defaults to the following:
* - numWorkers: 50% of the available logical processors, capped to 4. If no logical processors are available, defaults to 1.
* - wasmUrl: `"https://cdn.babylonjs.com/draco_wasm_wrapper_gltf.js"`
* - wasmBinaryUrl: `"https://cdn.babylonjs.com/draco_decoder_gltf.wasm"`
* - fallbackUrl: `"https://cdn.babylonjs.com/draco_decoder_gltf.js"`
*/
public static override Config: IDracoCodecConfiguration = {
wasmUrl: `${Tools._DefaultCdnUrl}/draco_wasm_wrapper_gltf.js`,
wasmBinaryUrl: `${Tools._DefaultCdnUrl}/draco_decoder_gltf.wasm`,
fallbackUrl: `${Tools._DefaultCdnUrl}/draco_decoder_gltf.js`,
};
export class DracoDecoderClass extends DracoCodec<DecoderModule> {
protected override readonly _defaultConfig: IDracoCodecConfiguration = DefaultDecoderConfig;

protected override _isModuleAvailable(): boolean {
return typeof DracoDecoderModule !== "undefined";
Expand All @@ -83,15 +48,6 @@ export class DracoDecoder extends DracoCodec<DecoderModule> {
return `${decodeMesh}(${workerFunction})()`;
}

/**
* Creates a new Draco decoder.
* @param config Optional override of the configuration for the DracoDecoder. If not provided, defaults to `DracoDecoder.Config`.
*/
constructor(config?: IDracoCodecConfiguration) {
// Order of final config will be config > DracoDecoder.Config.
super({ ...DracoDecoder.Config, ...(config ?? {}) });
}

/**
* Decode Draco compressed mesh data to mesh data.
* @param data The ArrayBuffer or ArrayBufferView for the Draco compression data
Expand Down Expand Up @@ -200,7 +156,8 @@ export class DracoDecoder extends DracoCodec<DecoderModule> {
});
}

throw new Error("Draco decoder module is not available");
throw new Error("Draco decoder module is not available. Have you called initialize()?");
// NOTE: Alternatively, we could initialize the module here if the user forgot.
}

/**
Expand Down Expand Up @@ -279,3 +236,36 @@ export class DracoDecoder extends DracoCodec<DecoderModule> {
return geometry;
}
}

/**
* @experimental
* Draco compression (https://google.github.io/draco/)
*
* DracoDecoder is a singleton for decoding Draco-compressed meshes.
* It is automatically constructed when the module is imported.
*
* To decode Draco compressed data, see the following example:
* ```javascript
* DracoDecoder.initialize(//Optional configuration//);
* var geometry = await DracoDecoder.decodeMeshToGeometryAsync(data);
// Perform any additional decoding here
* DracoDecoder.dispose();
* ```
*
* Before using the DracoDecoder, call initialize to ensure the decoder is ready.
* By default, the initialization configuration points to a copy of the Draco decoder files for glTF from the Babylon.js preview cdn https://preview.babylonjs.com/draco_wasm_wrapper_gltf.js.
*
* This configuration can be customized at initialization using the following code:
* ```javascript
* DracoDecoder.initialize({
* wasmUrl: "<url to the WebAssembly library>",
* wasmBinaryUrl: "<url to the WebAssembly binary>",
* fallbackUrl: "<url to the fallback JavaScript library>",
* });
* ```
*
* Draco has two versions, one for WebAssembly and one for JavaScript. The decoder configuration can be set to only support WebAssembly or only support the JavaScript version.
* Decoding will automatically fallback to the JavaScript version if WebAssembly version is not configured or if WebAssembly is not supported by the browser.
* For custom configurations, it is assumed that the developer has verified its availability in the current context (i.e., the WebAssembly version is correctly configured or a valid fallback exists.)
*/
export const DracoDecoder = new DracoDecoderClass();
2 changes: 1 addition & 1 deletion packages/dev/core/src/Meshes/Compression/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./dracoCompression";
export * from "./meshoptCompression";
export * from "./dracoDecoder";
export { DracoDecoder } from "./dracoDecoder";