@gg-web-engine/core 0.0.49 → 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 (35) 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/3d/entities/controllers/input/free-camera.controller.d.ts +9 -1
  6. package/dist/3d/entities/controllers/input/free-camera.controller.js +45 -12
  7. package/dist/3d/entities/controllers/input/orbit-camera.controller.d.ts +9 -8
  8. package/dist/3d/entities/controllers/input/orbit-camera.controller.js +46 -32
  9. package/dist/3d/entities/entity-3d.d.ts +6 -3
  10. package/dist/3d/entities/entity-3d.js +20 -17
  11. package/dist/3d/entities/raycast-vehicle-3d.entity.js +5 -5
  12. package/dist/3d/gg-3d-world.d.ts +3 -0
  13. package/dist/3d/gg-3d-world.js +21 -13
  14. package/dist/3d/loader.js +1 -1
  15. package/dist/base/clock/global-clock.d.ts +2 -6
  16. package/dist/base/clock/global-clock.js +16 -16
  17. package/dist/base/clock/i-clock.d.ts +14 -5
  18. package/dist/base/clock/i-clock.js +43 -1
  19. package/dist/base/clock/pausable-clock.d.ts +3 -13
  20. package/dist/base/clock/pausable-clock.js +8 -16
  21. package/dist/base/gg-world.d.ts +6 -0
  22. package/dist/base/gg-world.js +102 -65
  23. package/dist/base/inputs/keyboard.input.d.ts +1 -1
  24. package/dist/base/inputs/keyboard.input.js +10 -10
  25. package/dist/base/inputs/mouse.input.js +3 -2
  26. package/dist/dev/gg-console.ui.js +14 -4
  27. package/dist/dev/gg-debugger.ui.d.ts +2 -2
  28. package/dist/dev/gg-debugger.ui.js +13 -10
  29. package/dist/dev/gg-static.d.ts +6 -1
  30. package/dist/dev/gg-static.js +104 -25
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +3 -0
  33. package/dist/version.d.ts +1 -0
  34. package/dist/version.js +1 -0
  35. package/package.json +4 -3
package/dist/3d/loader.js CHANGED
@@ -99,7 +99,7 @@ export class Gg3dLoader {
99
99
  const loadOptions = Object.assign(Object.assign({}, defaultLoadOptions), options);
100
100
  const { resources, meta } = yield this.loadGgGlbResources(path, loadOptions.cachingStrategy);
101
101
  const result = {
102
- entities: resources.map(({ object3D, body }) => new Entity3d(object3D, body)),
102
+ entities: resources.map(x => new Entity3d({ object3D: x.object3D, objectBody: x.body })),
103
103
  meta,
104
104
  };
105
105
  if (loadOptions.loadProps) {
@@ -1,16 +1,12 @@
1
- import { Observable } from 'rxjs';
2
- import { PausableClock } from './pausable-clock';
3
1
  import { IClock } from './i-clock';
4
2
  /**
5
3
  * A singleton class, providing ability to track time, fire ticks, provide time elapsed + tick delta.
6
4
  * Starts as soon as accessed and counts time from 01/01/1970
7
5
  */
8
- export declare class GgGlobalClock implements IClock {
6
+ export declare class GgGlobalClock extends IClock {
9
7
  private static _instance;
10
8
  static get instance(): GgGlobalClock;
11
- private readonly _tick$;
12
- get tick$(): Observable<[number, number]>;
13
9
  get elapsedTime(): number;
14
- createChildClock(autoStart: boolean): PausableClock;
15
10
  private constructor();
11
+ dispose(): void;
16
12
  }
@@ -1,32 +1,32 @@
1
- import { animationFrameScheduler, of, Subject } from 'rxjs';
2
- import { map, repeat, tap } from 'rxjs/operators';
3
- import { PausableClock } from './pausable-clock';
1
+ import { IClock } from './i-clock';
2
+ const now = typeof performance === 'undefined' ? () => Date.now() : () => performance.now();
4
3
  /**
5
4
  * A singleton class, providing ability to track time, fire ticks, provide time elapsed + tick delta.
6
5
  * Starts as soon as accessed and counts time from 01/01/1970
7
6
  */
8
- export class GgGlobalClock {
7
+ export class GgGlobalClock extends IClock {
9
8
  static get instance() {
10
9
  if (!GgGlobalClock._instance) {
11
10
  GgGlobalClock._instance = new GgGlobalClock();
12
11
  }
13
12
  return GgGlobalClock._instance;
14
13
  }
15
- get tick$() {
16
- return this._tick$.pipe(map(([oldTime, newTime]) => [newTime, newTime - oldTime]));
17
- }
18
14
  get elapsedTime() {
19
- return (typeof performance === 'undefined' ? Date : performance).now();
20
- }
21
- createChildClock(autoStart) {
22
- return new PausableClock(autoStart, this);
15
+ return now();
23
16
  }
24
17
  constructor() {
25
- this._tick$ = new Subject();
18
+ super(null);
26
19
  let oldRelativeTime = this.elapsedTime;
27
- of(undefined, animationFrameScheduler)
28
- .pipe(repeat())
29
- .pipe(map(() => [oldRelativeTime, this.elapsedTime]), tap(([_, cur]) => (oldRelativeTime = cur)))
30
- .subscribe(this._tick$);
20
+ const tick = () => {
21
+ requestAnimationFrame(tick);
22
+ const prev = oldRelativeTime;
23
+ const cur = this.elapsedTime;
24
+ oldRelativeTime = cur;
25
+ this._tick$.next([prev, cur - prev]);
26
+ };
27
+ requestAnimationFrame(tick);
28
+ }
29
+ dispose() {
30
+ throw new Error('Cannot dispose global clock');
31
31
  }
32
32
  }
@@ -1,7 +1,16 @@
1
- import { Observable } from 'rxjs';
2
- import { PausableClock } from './pausable-clock';
3
- export interface IClock {
1
+ import { Observable, Subject } from 'rxjs';
2
+ export declare abstract class IClock {
3
+ readonly parent: IClock | null;
4
+ protected readonly _tick$: Subject<[number, number]>;
5
+ /**
6
+ * Observable stream of ticks, emitting an array containing the current time and the tick delta.
7
+ */
4
8
  get tick$(): Observable<[number, number]>;
5
- get elapsedTime(): number;
6
- createChildClock(autoStart: boolean): PausableClock;
9
+ abstract get elapsedTime(): number;
10
+ protected _children: IClock[];
11
+ get children(): IClock[];
12
+ protected constructor(parent: IClock | null);
13
+ addChild(clock: IClock): void;
14
+ removeChild(clock: IClock): void;
15
+ dispose(): void;
7
16
  }
@@ -1 +1,43 @@
1
- export {};
1
+ import { Subject } from 'rxjs';
2
+ export class IClock {
3
+ /**
4
+ * Observable stream of ticks, emitting an array containing the current time and the tick delta.
5
+ */
6
+ get tick$() {
7
+ return this._tick$.asObservable();
8
+ }
9
+ get children() {
10
+ return [...this._children];
11
+ }
12
+ constructor(parent) {
13
+ this.parent = parent;
14
+ this._tick$ = new Subject();
15
+ this._children = [];
16
+ if (parent) {
17
+ parent.addChild(this);
18
+ }
19
+ }
20
+ addChild(clock) {
21
+ if (clock.parent !== this) {
22
+ throw new Error('Incorrect child clock');
23
+ }
24
+ if (!this._children.includes(clock)) {
25
+ this._children.push(clock);
26
+ }
27
+ }
28
+ removeChild(clock) {
29
+ if (clock.parent !== this) {
30
+ throw new Error('Incorrect child clock');
31
+ }
32
+ this._children = this._children.filter(c => c !== clock);
33
+ }
34
+ dispose() {
35
+ if (this.parent) {
36
+ this.parent.removeChild(this);
37
+ }
38
+ for (const c of this._children) {
39
+ c.dispose();
40
+ }
41
+ this._tick$.complete();
42
+ }
43
+ }
@@ -1,17 +1,12 @@
1
- import { Observable, Subject } from 'rxjs';
1
+ import { Subject } from 'rxjs';
2
2
  import { IClock } from './i-clock';
3
3
  /**
4
4
  * A class providing the ability to track time, fire ticks, provide time elapsed, and tick delta with the ability to suspend/resume it.
5
5
  */
6
- export declare class PausableClock implements IClock {
6
+ export declare class PausableClock extends IClock {
7
7
  protected readonly parentClock: IClock;
8
8
  private tickSub;
9
9
  private readonly _internalTick$;
10
- private readonly _tick$;
11
- /**
12
- * Observable stream of ticks, emitting an array containing the current time and the tick delta.
13
- */
14
- get tick$(): Observable<[number, number]>;
15
10
  /**
16
11
  * Checks if the clock is currently running.
17
12
  */
@@ -54,12 +49,6 @@ export declare class PausableClock implements IClock {
54
49
  * @param parentClock The parent clock to synchronize with. Defaults to GgGlobalClock instance.
55
50
  */
56
51
  constructor(autoStart?: boolean, parentClock?: IClock);
57
- /**
58
- * Creates a child clock.
59
- * @param autoStart Indicates whether the child clock should start automatically.
60
- * @returns A new instance of PausableClock.
61
- */
62
- createChildClock(autoStart: boolean): PausableClock;
63
52
  /**
64
53
  * Starts the clock.
65
54
  */
@@ -84,4 +73,5 @@ export declare class PausableClock implements IClock {
84
73
  * Stops listening for ticks from the parent clock.
85
74
  */
86
75
  protected stopListeningTicks(): void;
76
+ dispose(): void;
87
77
  }
@@ -1,16 +1,11 @@
1
1
  import { filter, Subject } from 'rxjs';
2
2
  import { map, tap } from 'rxjs/operators';
3
3
  import { GgGlobalClock } from './global-clock';
4
+ import { IClock } from './i-clock';
4
5
  /**
5
6
  * A class providing the ability to track time, fire ticks, provide time elapsed, and tick delta with the ability to suspend/resume it.
6
7
  */
7
- export class PausableClock {
8
- /**
9
- * Observable stream of ticks, emitting an array containing the current time and the tick delta.
10
- */
11
- get tick$() {
12
- return this._tick$.asObservable();
13
- }
8
+ export class PausableClock extends IClock {
14
9
  /**
15
10
  * Checks if the clock is currently running.
16
11
  */
@@ -76,10 +71,10 @@ export class PausableClock {
76
71
  * @param parentClock The parent clock to synchronize with. Defaults to GgGlobalClock instance.
77
72
  */
78
73
  constructor(autoStart = false, parentClock = GgGlobalClock.instance) {
74
+ super(parentClock);
79
75
  this.parentClock = parentClock;
80
76
  this.tickSub = null;
81
77
  this._internalTick$ = new Subject();
82
- this._tick$ = new Subject();
83
78
  /**
84
79
  * Tick rate limiter. If set to 0 - tick rate is unlimited, 15 means "allow at most 15 ticks per second"
85
80
  */
@@ -103,14 +98,6 @@ export class PausableClock {
103
98
  Math.floor((elapsed * this.tickRateLimit) / 1000)), tap(([elapsed]) => (this.lastFiredTickElapsed = elapsed)))
104
99
  .subscribe(this._tick$);
105
100
  }
106
- /**
107
- * Creates a child clock.
108
- * @param autoStart Indicates whether the child clock should start automatically.
109
- * @returns A new instance of PausableClock.
110
- */
111
- createChildClock(autoStart) {
112
- return new PausableClock(autoStart, this);
113
- }
114
101
  /**
115
102
  * Starts the clock.
116
103
  */
@@ -170,4 +157,9 @@ export class PausableClock {
170
157
  (_a = this.tickSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
171
158
  this.tickSub = null;
172
159
  }
160
+ dispose() {
161
+ this.stopListeningTicks();
162
+ this._internalTick$.complete();
163
+ super.dispose();
164
+ }
173
165
  }
@@ -16,6 +16,7 @@ export declare abstract class GgWorld<D, R, VTypeDoc extends VisualTypeDocRepo<D
16
16
  readonly physicsWorld: PW;
17
17
  private static default_name_counter;
18
18
  private static _documentWorlds;
19
+ static readonly worldCreated$: Subject<GgWorld<any, any>>;
19
20
  static get documentWorlds(): GgWorld<any, any>[];
20
21
  readonly worldClock: PausableClock;
21
22
  readonly keyboardInput: KeyboardInput;
@@ -26,6 +27,7 @@ export declare abstract class GgWorld<D, R, VTypeDoc extends VisualTypeDocRepo<D
26
27
  readonly tickForwardTo$: Subject<IEntity | 'PHYSICS_WORLD'>;
27
28
  readonly tickForwardedTo$: Subject<IEntity | 'PHYSICS_WORLD'>;
28
29
  readonly paused$: Subject<boolean>;
30
+ readonly disposed$: Subject<void>;
29
31
  protected constructor(visualScene: VS, physicsWorld: PW);
30
32
  init(): Promise<void>;
31
33
  start(): void;
@@ -40,4 +42,8 @@ export declare abstract class GgWorld<D, R, VTypeDoc extends VisualTypeDocRepo<D
40
42
  position?: D, rotation?: R, material?: unknown): IPositionable<D, R> & IRenderableEntity<D, R, VTypeDoc>;
41
43
  addEntity(entity: IEntity): void;
42
44
  removeEntity(entity: IEntity, dispose?: boolean): void;
45
+ private onGgStaticInitialized;
46
+ protected registerConsoleCommands(ggstatic: {
47
+ registerConsoleCommand: (world: GgWorld<any, any> | null, command: string, handler: (...args: string[]) => Promise<string>, doc?: string) => void;
48
+ }): void;
43
49
  }
@@ -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();
@@ -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
+ ];
@@ -156,7 +156,7 @@ export class MouseInput extends IInput {
156
156
  if (this.options.canvas) {
157
157
  this.options.canvas.releasePointerCapture(event.pointerId);
158
158
  }
159
- this._element.removeEventListener('pointerup', onPointerUp);
159
+ window.removeEventListener('pointerup', onPointerUp);
160
160
  this._element.removeEventListener('pointercancel', onPointerUp);
161
161
  }
162
162
  this._state$.next(pointerLengthsStateMap[Math.min(pointers.length, 2)]);
@@ -169,7 +169,8 @@ export class MouseInput extends IInput {
169
169
  if (this.options.canvas) {
170
170
  this.options.canvas.setPointerCapture(event.pointerId);
171
171
  }
172
- 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);
173
174
  this._element.addEventListener('pointercancel', onPointerUp);
174
175
  }
175
176
  catch (err) {
@@ -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;