Skip to content

Commit fb299aa

Browse files
authored
chore(core): Convert luma object to singleton (#2083)
1 parent f1d0645 commit fb299aa

File tree

5 files changed

+70
-95
lines changed

5 files changed

+70
-95
lines changed

modules/core/src/adapter/device.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: MIT
33
// Copyright (c) vis.gl contributors
44

5-
import {VERSION} from '../init';
65
import {StatsManager, lumaStats} from '../utils/stats-manager';
76
import {log} from '../utils/log';
87
import {uid} from '../utils/uid';
@@ -300,8 +299,6 @@ export abstract class Device {
300299
return 'Device';
301300
}
302301

303-
static VERSION = VERSION;
304-
305302
constructor(props: DeviceProps) {
306303
this.props = {...Device.defaultProps, ...props};
307304
this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());

modules/core/src/adapter/luma.ts

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ import {StatsManager} from '../utils/stats-manager';
1010
import {lumaStats} from '../utils/stats-manager';
1111
import {log} from '../utils/log';
1212

13+
declare global {
14+
// eslint-disable-next-line no-var
15+
var luma: Luma;
16+
}
17+
18+
const STARTUP_MESSAGE = 'set luma.log.level=1 (or higher) to trace rendering';
19+
1320
const ERROR_MESSAGE =
1421
'No matching device found. Ensure `@luma.gl/webgl` and/or `@luma.gl/webgpu` modules are imported.';
1522

16-
const preregisteredAdapters = new Map<string, Adapter>();
17-
1823
/** Properties for creating a new device */
1924
export type CreateDeviceProps = DeviceProps & {
2025
/** Selects the type of device. `best-available` uses webgpu if available, then webgl. */
@@ -25,7 +30,8 @@ export type CreateDeviceProps = DeviceProps & {
2530
/** Properties for attaching an existing WebGL context or WebGPU device to a new luma Device */
2631
export type AttachDeviceProps = DeviceProps & {
2732
/** Externally created WebGL context or WebGPU device */
28-
handle: WebGL2RenderingContext; // | GPUDevice;
33+
handle: unknown; // WebGL2RenderingContext | GPUDevice | null;
34+
/** List of adapters. Will also search any pre-registered adapterss */
2935
adapters?: Adapter[];
3036
};
3137

@@ -34,37 +40,67 @@ export type AttachDeviceProps = DeviceProps & {
3440
* Register WebGPU and/or WebGL adapters (controls application bundle size)
3541
* Run-time selection of the first available Device
3642
*/
37-
export class luma {
43+
export class Luma {
3844
static defaultProps: Required<CreateDeviceProps> = {
3945
...Device.defaultProps,
4046
type: 'best-available',
4147
adapters: undefined!
4248
};
4349

44-
/** Global stats for all adapters */
45-
static stats: StatsManager = lumaStats;
50+
/** Global stats for all devices */
51+
readonly stats: StatsManager = lumaStats;
52+
53+
/**
54+
* Global log
55+
*
56+
* Assign luma.log.level in console to control logging: \
57+
* 0: none, 1: minimal, 2: verbose, 3: attribute/uniforms, 4: gl logs
58+
* luma.log.break[], set to gl funcs, luma.log.profile[] set to model names`;
59+
*/
60+
readonly log: Log = log;
61+
62+
/** Version of luma.gl */
63+
readonly VERSION: string =
64+
// Version detection using build plugin
65+
// @ts-expect-error no-undef
66+
typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'running from source';
67+
68+
protected preregisteredAdapters = new Map<string, Adapter>();
69+
70+
constructor() {
71+
if (globalThis.luma) {
72+
if (globalThis.luma.VERSION !== this.VERSION) {
73+
log.error(`Found luma.gl ${globalThis.luma.VERSION} while initialzing ${this.VERSION}`)();
74+
log.error(`'yarn why @luma.gl/core' can help identify the source of the conflict`)();
75+
throw new Error(`luma.gl - multiple versions detected: see console log`);
76+
}
77+
78+
log.error('This version of luma.gl has already been initialized')();
79+
}
4680

47-
/** Global log */
48-
static log: Log = log;
81+
log.log(1, `${this.VERSION} - ${STARTUP_MESSAGE}`)();
4982

50-
static registerAdapters(adapters: Adapter[]): void {
83+
globalThis.luma = this;
84+
}
85+
86+
registerAdapters(adapters: Adapter[]): void {
5187
for (const deviceClass of adapters) {
52-
preregisteredAdapters.set(deviceClass.type, deviceClass);
88+
this.preregisteredAdapters.set(deviceClass.type, deviceClass);
5389
}
5490
}
5591

5692
/** Get type strings for supported Devices */
57-
static getSupportedAdapters(adapters: Adapter[] = []): string[] {
58-
const adapterMap = getAdapterMap(adapters);
93+
getSupportedAdapters(adapters: Adapter[] = []): string[] {
94+
const adapterMap = this.getAdapterMap(adapters);
5995
return Array.from(adapterMap)
6096
.map(([, adapter]) => adapter)
6197
.filter(adapter => adapter.isSupported?.())
6298
.map(adapter => adapter.type);
6399
}
64100

65101
/** Get type strings for best available Device */
66-
static getBestAvailableAdapter(adapters: Adapter[] = []): 'webgpu' | 'webgl' | null {
67-
const adapterMap = getAdapterMap(adapters);
102+
getBestAvailableAdapter(adapters: Adapter[] = []): 'webgpu' | 'webgl' | null {
103+
const adapterMap = this.getAdapterMap(adapters);
68104
if (adapterMap.get('webgpu')?.isSupported?.()) {
69105
return 'webgpu';
70106
}
@@ -74,27 +110,27 @@ export class luma {
74110
return null;
75111
}
76112

77-
static setDefaultDeviceProps(props: CreateDeviceProps): void {
78-
Object.assign(luma.defaultProps, props);
113+
setDefaultDeviceProps(props: CreateDeviceProps): void {
114+
Object.assign(Luma.defaultProps, props);
79115
}
80116

81117
/** Creates a device. Asynchronously. */
82-
static async createDevice(props: CreateDeviceProps = {}): Promise<Device> {
83-
props = {...luma.defaultProps, ...props};
118+
async createDevice(props: CreateDeviceProps = {}): Promise<Device> {
119+
props = {...Luma.defaultProps, ...props};
84120

85121
// Should be handled by attach device
86122
// if (props.gl) {
87123
// props.type = 'webgl';
88124
// }
89125

90-
const adapterMap = getAdapterMap(props.adapters);
126+
const adapterMap = this.getAdapterMap(props.adapters);
91127

92128
let type: string = props.type || '';
93129
if (type === 'best-available') {
94-
type = luma.getBestAvailableAdapter(props.adapters) || type;
130+
type = this.getBestAvailableAdapter(props.adapters) || type;
95131
}
96132

97-
const adapters = getAdapterMap(props.adapters) || adapterMap;
133+
const adapters = this.getAdapterMap(props.adapters) || adapterMap;
98134

99135
const adapter = adapters.get(type);
100136
const device = await adapter?.create?.(props);
@@ -106,8 +142,8 @@ export class luma {
106142
}
107143

108144
/** Attach to an existing GPU API handle (WebGL2RenderingContext or GPUDevice). */
109-
static async attachDevice(props: AttachDeviceProps): Promise<Device> {
110-
const adapters = getAdapterMap(props.adapters);
145+
async attachDevice(props: AttachDeviceProps): Promise<Device> {
146+
const adapters = this.getAdapterMap(props.adapters);
111147

112148
// WebGL
113149
let type = '';
@@ -142,7 +178,7 @@ export class luma {
142178
* Used when attaching luma to a context from an external library does not support creating WebGL2 contexts.
143179
* (luma can only attach to WebGL2 contexts).
144180
*/
145-
static enforceWebGL2(enforce: boolean = true): void {
181+
enforceWebGL2(enforce: boolean = true): void {
146182
const prototype = HTMLCanvasElement.prototype as any;
147183
if (!enforce && prototype.originalGetContext) {
148184
// Reset the original getContext function
@@ -168,7 +204,7 @@ export class luma {
168204

169205
/** Convert a list of adapters to a map */
170206
protected getAdapterMap(adapters: Adapter[] = []): Map<string, Adapter> {
171-
const map = new Map(preregisteredAdapters);
207+
const map = new Map(this.preregisteredAdapters);
172208
for (const adapter of adapters) {
173209
map.set(adapter.type, adapter);
174210
}
@@ -178,23 +214,20 @@ export class luma {
178214
// DEPRECATED
179215

180216
/** @deprecated Use registerAdapters */
181-
static registerDevices(deviceClasses: any[]): void {
217+
registerDevices(deviceClasses: any[]): void {
182218
log.warn('luma.registerDevices() is deprecated, use luma.registerAdapters() instead');
183219
for (const deviceClass of deviceClasses) {
184220
const adapter = deviceClass.adapter as Adapter;
185221
if (adapter) {
186-
preregisteredAdapters.set(adapter.type, adapter);
222+
this.preregisteredAdapters.set(adapter.type, adapter);
187223
}
188224
}
189225
}
190226
}
191227

192-
/** Convert a list of adapters to a map */
193-
function getAdapterMap(adapters: Adapter[] = []): Map<string, Adapter> {
194-
const map = new Map<string, Adapter>(preregisteredAdapters);
195-
for (const deviceClass of adapters) {
196-
// assert(deviceClass.type && deviceClass.isSupported && deviceClass.create);
197-
map.set(deviceClass.type, deviceClass);
198-
}
199-
return map;
200-
}
228+
/**
229+
* Entry point to the luma.gl GPU abstraction
230+
* Register WebGPU and/or WebGL adapters (controls application bundle size)
231+
* Run-time selection of the first available Device
232+
*/
233+
export const luma = new Luma();

modules/core/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// SPDX-License-Identifier: MIT
33
// Copyright (c) vis.gl contributors
44

5-
export {VERSION} from './init';
6-
75
// MAIN API ACCESS POINT
86
export {luma} from './adapter/luma';
97

modules/core/src/init.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

modules/webgl/src/context/debug/spector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function initializeSpectorJS(props: SpectorProps): Spector | null {
5858
const {Spector} = globalThis.SPECTOR as any;
5959
spector = new Spector();
6060
if (globalThis.luma) {
61-
globalThis.luma.spector = spector;
61+
(globalThis.luma as any).spector = spector;
6262
}
6363
}
6464

0 commit comments

Comments
 (0)