@gg-web-engine/core 0.0.48 → 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/2d/entities/entity-2d.d.ts +6 -3
  2. package/dist/2d/entities/entity-2d.js +20 -17
  3. package/dist/2d/gg-2d-world.d.ts +3 -0
  4. package/dist/2d/gg-2d-world.js +21 -13
  5. package/dist/2d/models/shapes.d.ts +3 -1
  6. package/dist/3d/entities/controllers/input/car-keyboard-handling.controller.d.ts +1 -1
  7. package/dist/3d/entities/controllers/input/free-camera.controller.d.ts +32 -13
  8. package/dist/3d/entities/controllers/input/free-camera.controller.js +99 -45
  9. package/dist/3d/entities/controllers/input/gg-car-keyboard-handling.controller.d.ts +1 -1
  10. package/dist/3d/entities/controllers/input/orbit-camera.controller.d.ts +29 -9
  11. package/dist/3d/entities/controllers/input/orbit-camera.controller.js +64 -33
  12. package/dist/3d/entities/entity-3d.d.ts +6 -3
  13. package/dist/3d/entities/entity-3d.js +20 -17
  14. package/dist/3d/entities/raycast-vehicle-3d.entity.js +5 -5
  15. package/dist/3d/gg-3d-world.d.ts +3 -0
  16. package/dist/3d/gg-3d-world.js +21 -13
  17. package/dist/3d/loader.js +1 -1
  18. package/dist/3d/models/shapes.d.ts +3 -1
  19. package/dist/base/clock/global-clock.d.ts +2 -6
  20. package/dist/base/clock/global-clock.js +16 -16
  21. package/dist/base/clock/i-clock.d.ts +14 -5
  22. package/dist/base/clock/i-clock.js +43 -1
  23. package/dist/base/clock/pausable-clock.d.ts +3 -13
  24. package/dist/base/clock/pausable-clock.js +8 -16
  25. package/dist/base/gg-world.d.ts +6 -0
  26. package/dist/base/gg-world.js +102 -65
  27. package/dist/base/index.d.ts +1 -0
  28. package/dist/base/index.js +1 -0
  29. package/dist/base/inputs/keyboard.input.d.ts +1 -1
  30. package/dist/base/inputs/keyboard.input.js +10 -10
  31. package/dist/base/inputs/mouse.input.d.ts +1 -0
  32. package/dist/base/inputs/mouse.input.js +6 -2
  33. package/dist/base/pipes/gg-elastic.pipe.d.ts +5 -0
  34. package/dist/base/pipes/gg-elastic.pipe.js +44 -0
  35. package/dist/dev/gg-console.ui.js +14 -4
  36. package/dist/dev/gg-debugger.ui.d.ts +2 -2
  37. package/dist/dev/gg-debugger.ui.js +13 -10
  38. package/dist/dev/gg-static.d.ts +6 -1
  39. package/dist/dev/gg-static.js +104 -25
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +3 -0
  42. package/dist/version.d.ts +1 -0
  43. package/dist/version.js +1 -0
  44. package/package.json +4 -3
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { GgGlobalClock, IRendererEntity, KeyboardInput, TickOrder, } from '../base';
10
+ import { IRendererEntity, KeyboardInput, PausableClock, TickOrder, } from '../base';
11
11
  import { lastValueFrom, Subject, take } from 'rxjs';
12
12
  import { PerformanceMeterEntity } from '../dev';
13
13
  export class GgWorld {
@@ -17,7 +17,7 @@ export class GgWorld {
17
17
  constructor(visualScene, physicsWorld) {
18
18
  this.visualScene = visualScene;
19
19
  this.physicsWorld = physicsWorld;
20
- this.worldClock = GgGlobalClock.instance.createChildClock(false);
20
+ this.worldClock = new PausableClock(false);
21
21
  this.keyboardInput = new KeyboardInput();
22
22
  this.name = 'w0x' + (GgWorld.default_name_counter++).toString(16);
23
23
  this.children = [];
@@ -28,72 +28,18 @@ export class GgWorld {
28
28
  this.tickForwardTo$ = new Subject();
29
29
  this.tickForwardedTo$ = new Subject();
30
30
  this.paused$ = new Subject();
31
- GgWorld._documentWorlds.push(this);
31
+ this.disposed$ = new Subject();
32
32
  this.keyboardInput.start();
33
33
  if (window.ggstatic) {
34
- window.ggstatic.registerConsoleCommand(this, 'ph_timescale', (...args) => __awaiter(this, void 0, void 0, function* () {
35
- this.worldClock.timeScale = +args[0];
36
- return JSON.stringify(this.worldClock.timeScale);
37
- }), 'args: [float]; change time scale of physics engine. Default value is 1.0');
38
- window.ggstatic.registerConsoleCommand(this, 'ls_renderers', () => __awaiter(this, void 0, void 0, function* () {
39
- return this.children
40
- .filter(e => e instanceof IRendererEntity)
41
- .map(r => r.name)
42
- .join('\n');
43
- }), 'no args; print all renderers in selected world');
44
- window.ggstatic.registerConsoleCommand(this, 'dr_drawphysics', (...args) => __awaiter(this, void 0, void 0, function* () {
45
- const value = ['1', 'true', '+'].includes(args[0]);
46
- const rendererName = args[1];
47
- let renderer;
48
- if (rendererName) {
49
- renderer = this.children.find(x => x instanceof IRendererEntity && x.name === rendererName);
50
- }
51
- else {
52
- renderer = this.children.find(x => x instanceof IRendererEntity);
53
- }
54
- if (renderer) {
55
- renderer.physicsDebugViewActive = value;
56
- return '' + value;
57
- }
58
- return 'false';
59
- }), 'args: [0 or 1] or [0 or 1, string]; turn on/off physics debug view. Second argument expects renderer ' +
60
- 'name, if not provided first renderer will be picked. Look up for renderer names using command "ls_renderers"');
61
- window.ggstatic.registerConsoleCommand(this, 'performance_report', (...args) => __awaiter(this, void 0, void 0, function* () {
62
- let mode = 'avg';
63
- let samples = 20;
64
- for (let i = 0; i < 2; i++) {
65
- if (['avg', 'peak'].includes(args[i])) {
66
- mode = args[i];
67
- }
68
- else if (!isNaN(+args[i])) {
69
- samples = +args[i];
70
- }
71
- }
72
- const meter = new PerformanceMeterEntity(samples, 250);
73
- this.addEntity(meter);
74
- yield lastValueFrom(this.worldClock.tick$.pipe(take(samples)));
75
- const report = mode === 'avg' ? meter.avgReport : meter.peakReport;
76
- this.removeEntity(meter);
77
- const renderItems = report.entries.map(([name, value]) => `<span style='color:lightgray;'>${name}:</span>` +
78
- new Array(Math.max(0, 26 - name.length)).join('&nbsp;') +
79
- `${value.toFixed(2)} ms` +
80
- (mode === 'avg' ? ` (${((value * 100) / report.totalTime).toFixed(2)}%)` : ''));
81
- let totalColor = 'lightgreen';
82
- if (report.totalTime > 12) {
83
- totalColor = report.totalTime < 16 ? 'yellow' : 'red';
84
- }
85
- const title = `${mode === 'avg' ? 'Average' : 'Peak'} Frame time`;
86
- renderItems.unshift(title +
87
- ':' +
88
- new Array(Math.max(0, 26 - title.length)).join('&nbsp;') +
89
- `<span style='color:${totalColor};'>${report.totalTime.toFixed(2)} ms</span>`);
90
- renderItems.unshift(`Performance report (${samples} samples)`);
91
- return renderItems.join('\n');
92
- }), 'args: [int, avg|peak]; [avg|peak, int]; [avg|peak]; [int] or []; measure how much time was spent per ' +
93
- 'entity in world. Arguments are samples amount (20 by default) and "peak" or "avg" choice, both arguments are ' +
94
- 'optional. "avg" report sorts entities by average time consumed, "peak" records highest value for each entity');
34
+ this.registerConsoleCommands(window.ggstatic);
35
+ }
36
+ else {
37
+ this.onGgStaticInitialized = this.onGgStaticInitialized.bind(this);
38
+ window.addEventListener('ggstatic_added', this.onGgStaticInitialized);
95
39
  }
96
40
  this.worldClock.paused$.subscribe(this.paused$);
41
+ GgWorld._documentWorlds.push(this);
42
+ GgWorld.worldCreated$.next(this);
97
43
  }
98
44
  init() {
99
45
  return __awaiter(this, void 0, void 0, function* () {
@@ -145,12 +91,15 @@ export class GgWorld {
145
91
  return this.worldClock.elapsedTime;
146
92
  }
147
93
  createClock(autoStart) {
148
- return this.worldClock.createChildClock(autoStart);
94
+ return new PausableClock(autoStart, this.worldClock);
149
95
  }
150
96
  dispose() {
151
97
  if (window.ggstatic) {
152
98
  window.ggstatic.deregisterWorldCommands(this);
153
99
  }
100
+ else {
101
+ window.removeEventListener('ggstatic_added', this.onGgStaticInitialized);
102
+ }
154
103
  this.worldClock.stop();
155
104
  this.keyboardInput.stop();
156
105
  this.tickStarted$.complete();
@@ -164,6 +113,9 @@ export class GgWorld {
164
113
  this.tickListeners.splice(0, this.tickListeners.length);
165
114
  this.physicsWorld.dispose();
166
115
  this.visualScene.dispose();
116
+ GgWorld._documentWorlds.splice(GgWorld._documentWorlds.indexOf(this), 1);
117
+ this.disposed$.next();
118
+ this.disposed$.complete();
167
119
  }
168
120
  addEntity(entity) {
169
121
  if (!!entity.world) {
@@ -188,6 +140,91 @@ export class GgWorld {
188
140
  entity.dispose();
189
141
  }
190
142
  }
143
+ onGgStaticInitialized() {
144
+ window.removeEventListener('ggstatic_added', this.onGgStaticInitialized);
145
+ this.registerConsoleCommands(window.ggstatic);
146
+ }
147
+ registerConsoleCommands(ggstatic) {
148
+ ggstatic.registerConsoleCommand(this, 'timescale', (...args) => __awaiter(this, void 0, void 0, function* () {
149
+ if (!isNaN(+args[0])) {
150
+ this.worldClock.timeScale = +args[0];
151
+ }
152
+ return this.worldClock.timeScale.toString();
153
+ }), 'args: [ float? ]; Get current time scale of selected world clock or set it.' +
154
+ ' Default value is 1.0 (no time scale applied)');
155
+ ggstatic.registerConsoleCommand(this, 'fps_limit', (...args) => __awaiter(this, void 0, void 0, function* () {
156
+ if (!isNaN(+args[0])) {
157
+ this.worldClock.tickRateLimit = +args[0];
158
+ }
159
+ return this.worldClock.tickRateLimit.toString();
160
+ }), 'args: [ int? ]; Get current tick rate limit of selected world clock or set it. 0 means no limit applied');
161
+ ggstatic.registerConsoleCommand(this, 'renderers', () => __awaiter(this, void 0, void 0, function* () {
162
+ return this.children
163
+ .filter(e => e instanceof IRendererEntity)
164
+ .map(r => r.name)
165
+ .join('\n');
166
+ }), 'no args; Print all renderers in selected world');
167
+ ggstatic.registerConsoleCommand(this, 'debug_view', (...args) => __awaiter(this, void 0, void 0, function* () {
168
+ let value = 'toggle';
169
+ let rendererName = undefined;
170
+ for (let arg of args) {
171
+ if (['1', '0'].includes(arg)) {
172
+ value = arg === '1';
173
+ }
174
+ else {
175
+ rendererName = arg;
176
+ }
177
+ }
178
+ let renderer = this.children.find(x => x instanceof IRendererEntity && (!rendererName || x.name === rendererName));
179
+ if (renderer) {
180
+ renderer.physicsDebugViewActive = value === 'toggle' ? !renderer.physicsDebugViewActive : value;
181
+ return renderer.physicsDebugViewActive ? '1' : '0';
182
+ }
183
+ else if (rendererName) {
184
+ throw new Error(`Renderer with name "${rendererName}" not found`);
185
+ }
186
+ else {
187
+ throw new Error(`No renderer found`);
188
+ }
189
+ }), 'args: [ 0|1?, string? ]; Turn on/off physics debug view, skip first argument to toggle value.' +
190
+ ' Second argument expects renderer name, if not provided first renderer will be picked.' +
191
+ ' Use "renderers" to get list of renderers in the world');
192
+ ggstatic.registerConsoleCommand(this, 'performance', (...args) => __awaiter(this, void 0, void 0, function* () {
193
+ let mode = 'avg';
194
+ let samples = 20;
195
+ for (let arg of args) {
196
+ if (['avg', 'peak'].includes(arg)) {
197
+ mode = arg;
198
+ }
199
+ else if (!isNaN(+arg)) {
200
+ samples = +arg;
201
+ }
202
+ }
203
+ const meter = new PerformanceMeterEntity(samples, 250);
204
+ this.addEntity(meter);
205
+ yield lastValueFrom(this.worldClock.tick$.pipe(take(samples)));
206
+ const report = mode === 'avg' ? meter.avgReport : meter.peakReport;
207
+ this.removeEntity(meter);
208
+ const renderItems = report.entries.map(([name, value]) => `<span style='color:lightgray;'>${name}:</span>` +
209
+ new Array(Math.max(0, 26 - name.length)).join('&nbsp;') +
210
+ `${value.toFixed(2)} ms` +
211
+ (mode === 'avg' ? ` (${((value * 100) / report.totalTime).toFixed(2)}%)` : ''));
212
+ let totalColor = 'lightgreen';
213
+ if (report.totalTime > 12) {
214
+ totalColor = report.totalTime < 16 ? 'yellow' : 'red';
215
+ }
216
+ const title = `${mode === 'avg' ? 'Average' : 'Peak'} Frame time`;
217
+ renderItems.unshift(title +
218
+ ':' +
219
+ new Array(Math.max(0, 26 - title.length)).join('&nbsp;') +
220
+ `<span style='color:${totalColor};'>${report.totalTime.toFixed(2)} ms</span>`);
221
+ renderItems.unshift(`Performance report (${samples} samples)`);
222
+ return renderItems.join('\n');
223
+ }), 'args: [ int?, avg|peak? ]; Measure how much time was spent per ' +
224
+ 'entity in world. Arguments are samples amount (20 by default) and "peak" or "avg" choice, both arguments are ' +
225
+ 'optional. "avg" report sorts entities by average time consumed, "peak" records highest value for each entity');
226
+ }
191
227
  }
192
228
  GgWorld.default_name_counter = 0;
193
229
  GgWorld._documentWorlds = [];
230
+ GgWorld.worldCreated$ = new Subject();
@@ -33,4 +33,5 @@ export * from './math/quaternion';
33
33
  export * from './math/numbers';
34
34
  export * from './math/matrix4';
35
35
  export * from './math/splines';
36
+ export * from './pipes/gg-elastic.pipe';
36
37
  export * from './gg-world';
@@ -33,4 +33,5 @@ export * from './math/quaternion';
33
33
  export * from './math/numbers';
34
34
  export * from './math/matrix4';
35
35
  export * from './math/splines';
36
+ export * from './pipes/gg-elastic.pipe';
36
37
  export * from './gg-world';
@@ -14,7 +14,7 @@ export declare class KeyboardInput extends IInput {
14
14
  /**
15
15
  * Which element types should filter key downs when focused
16
16
  */
17
- externalFocusBlacklist: {
17
+ static externalFocusBlacklist: {
18
18
  new (): HTMLElement;
19
19
  }[];
20
20
  /**
@@ -17,15 +17,6 @@ export class KeyboardInput extends IInput {
17
17
  * Flag which disables handling key downs, when document has some "typeable" element focused
18
18
  */
19
19
  this.skipKeyDownsOnExternalFocus = true;
20
- /**
21
- * Which element types should filter key downs when focused
22
- */
23
- this.externalFocusBlacklist = [
24
- HTMLInputElement,
25
- HTMLTextAreaElement,
26
- HTMLSelectElement,
27
- HTMLButtonElement,
28
- ];
29
20
  this.handleKeys = this.handleKeys.bind(this);
30
21
  this.resetAllKeys = this.resetAllKeys.bind(this);
31
22
  this.onPointerLockChange = this.onPointerLockChange.bind(this);
@@ -127,7 +118,7 @@ export class KeyboardInput extends IInput {
127
118
  }
128
119
  const pressed = e.type == 'keydown';
129
120
  if (pressed && this.skipKeyDownsOnExternalFocus && document.activeElement) {
130
- for (const k of this.externalFocusBlacklist) {
121
+ for (const k of KeyboardInput.externalFocusBlacklist) {
131
122
  if (document.activeElement instanceof k) {
132
123
  return;
133
124
  }
@@ -151,3 +142,12 @@ export class KeyboardInput extends IInput {
151
142
  }
152
143
  }
153
144
  }
145
+ /**
146
+ * Which element types should filter key downs when focused
147
+ */
148
+ KeyboardInput.externalFocusBlacklist = [
149
+ HTMLInputElement,
150
+ HTMLTextAreaElement,
151
+ HTMLSelectElement,
152
+ HTMLButtonElement,
153
+ ];
@@ -59,6 +59,7 @@ export declare class MouseInput extends IInput<[], [unlockPointer?: boolean]> {
59
59
  private _wheel$;
60
60
  get wheel$(): Observable<number>;
61
61
  get isPointerLocked(): boolean;
62
+ get isPointerLocked$(): Observable<boolean>;
62
63
  private readonly options;
63
64
  private _delta$;
64
65
  private _position$;
@@ -67,6 +67,9 @@ export class MouseInput extends IInput {
67
67
  get isPointerLocked() {
68
68
  return !!document.pointerLockElement;
69
69
  }
70
+ get isPointerLocked$() {
71
+ return fromEvent(document, 'pointerlockchange').pipe(map(() => this.isPointerLocked));
72
+ }
70
73
  get state() {
71
74
  return this._state$.getValue();
72
75
  }
@@ -153,7 +156,7 @@ export class MouseInput extends IInput {
153
156
  if (this.options.canvas) {
154
157
  this.options.canvas.releasePointerCapture(event.pointerId);
155
158
  }
156
- this._element.removeEventListener('pointerup', onPointerUp);
159
+ window.removeEventListener('pointerup', onPointerUp);
157
160
  this._element.removeEventListener('pointercancel', onPointerUp);
158
161
  }
159
162
  this._state$.next(pointerLengthsStateMap[Math.min(pointers.length, 2)]);
@@ -166,7 +169,8 @@ export class MouseInput extends IInput {
166
169
  if (this.options.canvas) {
167
170
  this.options.canvas.setPointerCapture(event.pointerId);
168
171
  }
169
- this._element.addEventListener('pointerup', onPointerUp);
172
+ // use window instead of this._element to handle case when mouse was released over other element
173
+ window.addEventListener('pointerup', onPointerUp);
170
174
  this._element.addEventListener('pointercancel', onPointerUp);
171
175
  }
172
176
  catch (err) {
@@ -0,0 +1,5 @@
1
+ import { Observable, OperatorFunction } from 'rxjs';
2
+ export declare function ggElastic<T>(tick$: Observable<[number, number]>, // [elapsed, delta]
3
+ elasticity: number, // Elasticity parameter
4
+ mix: (a: T, b: T, factor: number) => T, // Mixing function
5
+ equals: (a: T, b: T) => boolean): OperatorFunction<T, T>;
@@ -0,0 +1,44 @@
1
+ import { filter, map, scan, switchMap } from 'rxjs';
2
+ export function ggElastic(tick$, // [elapsed, delta]
3
+ elasticity, // Elasticity parameter
4
+ mix, // Mixing function
5
+ equals) {
6
+ return (source$) => {
7
+ return source$.pipe(
8
+ // Track the target value
9
+ scan((acc, newValue) => {
10
+ var _a;
11
+ return ({
12
+ targetValue: newValue,
13
+ currentValue: (_a = acc.currentValue) !== null && _a !== void 0 ? _a : newValue, // Initialize to the first value on startup
14
+ hasReachedTarget: false, // Reset reach status on new target
15
+ });
16
+ }, { targetValue: null, currentValue: null, hasReachedTarget: false }),
17
+ // Switch to using tick$ for timing, emitting interpolated values with each tick
18
+ switchMap(state => tick$.pipe(map(([_, delta]) => {
19
+ if (state.targetValue === null || state.currentValue === null) {
20
+ return state.currentValue; // If we have no values, output nothing
21
+ }
22
+ // Skip processing if we’ve reached the target and stop emitting
23
+ if (state.hasReachedTarget) {
24
+ return undefined; // Stop emitting by returning `undefined`
25
+ }
26
+ // Adjust factor based on elasticity and delta time
27
+ const factor = 1 - Math.exp(-delta / elasticity);
28
+ // Blend halfway towards the target on each tick
29
+ const newInterpolatedValue = mix(state.currentValue, state.targetValue, factor);
30
+ // Update the currentValue to the new interpolated position
31
+ state.currentValue = newInterpolatedValue;
32
+ // Check if the interpolated value is "close enough" to the target
33
+ if (equals(newInterpolatedValue, state.targetValue)) {
34
+ state.hasReachedTarget = true; // Mark as reached
35
+ return state.targetValue; // Emit exact target value
36
+ }
37
+ return newInterpolatedValue;
38
+ }))),
39
+ // Filter out `undefined` emissions (to stop emitting when settled)
40
+ map(value => (value !== undefined ? value : null)),
41
+ // Remove `null` values (after stopping)
42
+ filter(value => value !== null));
43
+ };
44
+ }
@@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import { VERSION } from '../version';
10
11
  export class GgConsoleUI {
11
12
  constructor() {
12
13
  this.output = `
@@ -20,11 +21,12 @@ export class GgConsoleUI {
20
21
  ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
21
22
  ░ ░ ░ ░ ░ ░
22
23
 
24
+ Version: ${VERSION}
23
25
  >>> https://github.com/AndyGura/gg-web-engine <<<
24
26
  Welcome to GG web engine UI console.
25
27
  Enter command in input below.
26
28
 
27
- List of available commands: `.replace(/ /g, '&nbsp;') + `<span style="color:yellow">ls_commands</span>`;
29
+ List of available commands: `.replace(/ /g, '&nbsp;') + `<span style='color:yellow'>commands</span>`;
28
30
  this.commandHistory = [];
29
31
  this.currentCommandIndex = 0; // for repeating command using up/down arrow keys
30
32
  this.elements = null;
@@ -98,9 +100,14 @@ List of available commands: `.replace(/ /g, '&nbsp;') + `<span style="color:yell
98
100
  if (value.trim() === '') {
99
101
  return;
100
102
  }
101
- let autocompletion = window.ggstatic.availableCommands.find((c) => c[0].startsWith(value));
102
- if (autocompletion && autocompletion[0].length > value.length) {
103
- this.elements.input.value = autocompletion[0];
103
+ let autocompletion = null;
104
+ for (let [command] of window.ggstatic.availableCommands) {
105
+ if (command.startsWith(value) && (!autocompletion || autocompletion.length > command.length)) {
106
+ autocompletion = command;
107
+ }
108
+ }
109
+ if (autocompletion && autocompletion.length > value.length) {
110
+ this.elements.input.value = autocompletion;
104
111
  this.elements.input.setSelectionRange(value.length, this.elements.input.value.length);
105
112
  }
106
113
  };
@@ -135,6 +142,9 @@ List of available commands: `.replace(/ /g, '&nbsp;') + `<span style="color:yell
135
142
  onInput() {
136
143
  return __awaiter(this, void 0, void 0, function* () {
137
144
  const command = this.elements.input.value;
145
+ if (command.length === 0) {
146
+ return;
147
+ }
138
148
  this.elements.input.value = '';
139
149
  this.stdout('\n> ' + command);
140
150
  this.stdout('\n' + (yield window.ggstatic.console(command)));
@@ -4,11 +4,11 @@ export declare class GgDebuggerUI {
4
4
  private statsRemoved$;
5
5
  get showStats(): boolean;
6
6
  private currentWorld;
7
- setShowStats(selectedWorld: GgWorld<any, any>, value: boolean): void;
7
+ setShowStats(selectedWorld: GgWorld<any, any> | null, value: boolean): void;
8
8
  private debugControlsRemoved$;
9
9
  private viewUpdated$;
10
10
  get showDebugControls(): boolean;
11
- setShowDebugControls(selectedWorld: GgWorld<any, any>, value: boolean): void;
11
+ setShowDebugControls(selectedWorld: GgWorld<any, any> | null, value: boolean): void;
12
12
  private snapshot;
13
13
  perfStatsMode: 'AVG' | 'PEAK';
14
14
  private performanceStatsSnapshot;
@@ -69,7 +69,7 @@ export class GgDebuggerUI {
69
69
  }
70
70
  }
71
71
  this.currentWorld = selectedWorld;
72
- if (value) {
72
+ if (selectedWorld && value) {
73
73
  const stats = new Stats();
74
74
  this.ui.stats = stats;
75
75
  stats.dom.style.left = 'unset';
@@ -139,9 +139,10 @@ export class GgDebuggerUI {
139
139
  }
140
140
  }
141
141
  makeSnapshot() {
142
+ var _a, _b;
142
143
  const renderers = [];
143
144
  let performanceMeter = null;
144
- for (const e of this.currentWorld.children) {
145
+ for (const e of ((_a = this.currentWorld) === null || _a === void 0 ? void 0 : _a.children) || []) {
145
146
  if (e instanceof IRendererEntity) {
146
147
  renderers.push(e);
147
148
  }
@@ -150,7 +151,7 @@ export class GgDebuggerUI {
150
151
  }
151
152
  }
152
153
  return {
153
- timeScale: this.currentWorld.worldClock.timeScale,
154
+ timeScale: ((_b = this.currentWorld) === null || _b === void 0 ? void 0 : _b.worldClock.timeScale) || NaN,
154
155
  renderers: renderers.map(r => ({
155
156
  name: r.name,
156
157
  entity: r,
@@ -160,9 +161,10 @@ export class GgDebuggerUI {
160
161
  };
161
162
  }
162
163
  makePerformanceStatsSnapshot() {
164
+ var _a;
163
165
  if (!this.snapshot.performanceStatsEnabled)
164
166
  return null;
165
- let performanceMeter = this.currentWorld.children.find(e => e instanceof PerformanceMeterEntity);
167
+ let performanceMeter = (((_a = this.currentWorld) === null || _a === void 0 ? void 0 : _a.children) || []).find(e => e instanceof PerformanceMeterEntity);
166
168
  if (performanceMeter) {
167
169
  if (this.perfStatsMode === 'AVG') {
168
170
  return performanceMeter.avgReport;
@@ -178,19 +180,19 @@ export class GgDebuggerUI {
178
180
  for (const { entity, physicsDebugViewActive } of this.snapshot.renderers) {
179
181
  html += `
180
182
  <div ${this.css}>
181
- <input type='checkbox' name='checkbox' id='physics_debugger_checkbox_id_${entity.name}' value='1'${physicsDebugViewActive ? ' checked' : ''}>
183
+ <input type='checkbox' name='checkbox' id='physics_debugger_checkbox_id_${entity.name}' onkeydown='event.preventDefault()' value='1'${physicsDebugViewActive ? ' checked' : ''}>
182
184
  <label for='physics_debugger_checkbox_id_${entity.name}' style='user-select: none;'>Physics debugger${this.snapshot.renderers.length > 1 ? ' (' + entity.name + ')' : ''}</label>
183
185
  </div>`;
184
186
  }
185
187
  html += `
186
188
  <div ${this.css}>
187
- <input id='time_scale_slider' type='range' min='0' max='5' step='0.01' style='flex-grow:1' value='${this.snapshot.timeScale}'/>
189
+ <input id='time_scale_slider' type='range' min='0' max='5' step='0.01' onkeydown='event.preventDefault()' style='flex-grow:1' value='${this.snapshot.timeScale}'/>
188
190
  <label for='time_scale_slider' style='user-select: none;'>Time scale</label>
189
191
  </div>`;
190
192
  html +=
191
193
  `
192
194
  <div ${this.css}>
193
- <input type='checkbox' name='checkbox' id='perf_stats_checkbox_id' value='1'${this.snapshot.performanceStatsEnabled ? ' checked' : ''}>
195
+ <input type='checkbox' name='checkbox' id='perf_stats_checkbox_id' onkeydown='event.preventDefault()' value='1'${this.snapshot.performanceStatsEnabled ? ' checked' : ''}>
194
196
  <label for='perf_stats_checkbox_id' style='user-select: none;'>Entities performance distribution</label>
195
197
  </div>
196
198
  ` +
@@ -233,12 +235,13 @@ export class GgDebuggerUI {
233
235
  fromEvent(document.getElementById('perf_stats_checkbox_id'), 'change')
234
236
  .pipe(takeUntil(this.debugControlsRemoved$), takeUntil(this.viewUpdated$))
235
237
  .subscribe(e => {
236
- const entity = this.currentWorld.children.find(x => x instanceof PerformanceMeterEntity);
238
+ var _a;
239
+ const entity = (((_a = this.currentWorld) === null || _a === void 0 ? void 0 : _a.children) || []).find(x => x instanceof PerformanceMeterEntity);
237
240
  if (e.target.checked == !entity) {
238
241
  if (entity) {
239
242
  this.currentWorld.removeEntity(entity);
240
243
  }
241
- else {
244
+ else if (this.currentWorld) {
242
245
  this.currentWorld.addEntity(new PerformanceMeterEntity());
243
246
  }
244
247
  }
@@ -265,7 +268,7 @@ export class GgDebuggerUI {
265
268
  html += `
266
269
  <div ${pCss}>
267
270
  <label for='stat_progress' ${labelCss}>${total.toFixed(2)}ms</label>
268
- <progress id="stat_progress" ${progressCss} max="16" value="${total}"></progress>
271
+ <progress id='stat_progress' ${progressCss} max='16' value='${total}'></progress>
269
272
  <span ${spanCss}>Total frame time</span>
270
273
  </div>`;
271
274
  for (const [i, [name, value]] of this.performanceStatsSnapshot.entries.entries()) {
@@ -7,12 +7,17 @@ export declare class GgStatic {
7
7
  get devConsoleEnabled(): boolean;
8
8
  consoleKeyPressEventListener: (event: KeyboardEvent) => void;
9
9
  set devConsoleEnabled(value: boolean);
10
+ toggleDevConsole(value: boolean): void;
11
+ private showStats$;
10
12
  get showStats(): boolean;
11
13
  set showStats(value: boolean);
14
+ private showDebugControls$;
12
15
  get showDebugControls(): boolean;
13
16
  set showDebugControls(value: boolean);
14
- private _selectedWorld;
17
+ private _selectedWorld$;
15
18
  get selectedWorld(): GgWorld<any, any> | null;
19
+ private set selectedWorld(value);
20
+ private autoAssignSelectedWorld;
16
21
  get availableCommands(): [string, {
17
22
  handler: (...args: string[]) => Promise<string>;
18
23
  doc?: string;