@gg-web-engine/core 0.0.39 → 0.0.40
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.
- package/dist/2d/components/rendering/i-visual-scene-2d.component.d.ts +1 -1
- package/dist/2d/entities/i-renderable-2d.entity.d.ts +3 -1
- package/dist/2d/entities/i-renderable-2d.entity.js +7 -0
- package/dist/2d/gg-2d-world.d.ts +1 -0
- package/dist/3d/components/rendering/i-visual-scene-3d.component.d.ts +1 -1
- package/dist/3d/entities/i-renderable-3d.entity.d.ts +3 -1
- package/dist/3d/entities/i-renderable-3d.entity.js +7 -0
- package/dist/3d/entities/map-graph-3d.entity.d.ts +2 -3
- package/dist/3d/entities/map-graph-3d.entity.js +10 -7
- package/dist/3d/entities/raycast-vehicle-3d.entity.d.ts +2 -0
- package/dist/3d/entities/raycast-vehicle-3d.entity.js +15 -1
- package/dist/3d/gg-3d-world.d.ts +1 -0
- package/dist/base/clock/pausable-clock.d.ts +6 -0
- package/dist/base/clock/pausable-clock.js +14 -3
- package/dist/base/components/rendering/i-renderer.component.d.ts +2 -2
- package/dist/base/entities/controllers/inline-controller.d.ts +1 -1
- package/dist/base/entities/controllers/inline-controller.js +4 -1
- package/dist/base/gg-world.d.ts +8 -3
- package/dist/base/gg-world.js +55 -14
- package/dist/base/inputs/mouse.input.js +9 -4
- package/dist/dev/gg-debugger.ui.d.ts +5 -0
- package/dist/dev/gg-debugger.ui.js +129 -8
- package/dist/dev/gg-static.js +12 -4
- package/dist/dev/index.d.ts +1 -0
- package/dist/dev/index.js +1 -0
- package/dist/dev/performance-meter.entity.d.ts +21 -0
- package/dist/dev/performance-meter.entity.js +120 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IVisualSceneComponent, Point2, RendererOptions } from '../../../base';
|
|
2
2
|
import { VisualTypeDocRepo2D } from '../../gg-2d-world';
|
|
3
3
|
export interface IVisualScene2dComponent<TypeDoc extends VisualTypeDocRepo2D = VisualTypeDocRepo2D> extends IVisualSceneComponent<Point2, number, TypeDoc> {
|
|
4
|
-
createRenderer(canvas?: HTMLCanvasElement, rendererOptions?: Partial<RendererOptions>): TypeDoc['renderer'];
|
|
4
|
+
createRenderer(canvas?: HTMLCanvasElement, rendererOptions?: Partial<RendererOptions & TypeDoc['rendererExtraOpts']>): TypeDoc['renderer'];
|
|
5
5
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { IRenderableEntity, Point2 } from '../../base';
|
|
2
|
-
import { PhysicsTypeDocRepo2D, VisualTypeDocRepo2D } from '../gg-2d-world';
|
|
2
|
+
import { Gg2dWorld, PhysicsTypeDocRepo2D, VisualTypeDocRepo2D } from '../gg-2d-world';
|
|
3
3
|
export declare abstract class IRenderable2dEntity<TypeDoc extends VisualTypeDocRepo2D = VisualTypeDocRepo2D, PTypeDoc extends PhysicsTypeDocRepo2D = PhysicsTypeDocRepo2D> extends IRenderableEntity<Point2, number, TypeDoc, PTypeDoc> {
|
|
4
|
+
protected _world: Gg2dWorld<TypeDoc, PTypeDoc> | null;
|
|
5
|
+
get world(): Gg2dWorld<TypeDoc, PTypeDoc> | null;
|
|
4
6
|
}
|
package/dist/2d/gg-2d-world.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare type VisualTypeDocRepo2D = {
|
|
|
13
13
|
factory: IDisplayObject2dComponentFactory;
|
|
14
14
|
displayObject: IDisplayObject2dComponent;
|
|
15
15
|
renderer: IRenderer2dComponent;
|
|
16
|
+
rendererExtraOpts: {};
|
|
16
17
|
texture: unknown;
|
|
17
18
|
};
|
|
18
19
|
export declare type PhysicsTypeDocRepo2D = {
|
|
@@ -2,5 +2,5 @@ import { IVisualSceneComponent, Point3, Point4, RendererOptions } from '../../..
|
|
|
2
2
|
import { VisualTypeDocRepo3D } from '../../gg-3d-world';
|
|
3
3
|
export interface IVisualScene3dComponent<TypeDoc extends VisualTypeDocRepo3D = VisualTypeDocRepo3D> extends IVisualSceneComponent<Point3, Point4, TypeDoc> {
|
|
4
4
|
readonly loader: TypeDoc['loader'];
|
|
5
|
-
createRenderer(camera: TypeDoc['camera'], canvas?: HTMLCanvasElement, rendererOptions?: Partial<RendererOptions>): TypeDoc['renderer'];
|
|
5
|
+
createRenderer(camera: TypeDoc['camera'], canvas?: HTMLCanvasElement, rendererOptions?: Partial<RendererOptions & TypeDoc['rendererExtraOpts']>): TypeDoc['renderer'];
|
|
6
6
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { IRenderableEntity, Point3, Point4 } from '../../base';
|
|
2
|
-
import { PhysicsTypeDocRepo3D, VisualTypeDocRepo3D } from '../gg-3d-world';
|
|
2
|
+
import { Gg3dWorld, PhysicsTypeDocRepo3D, VisualTypeDocRepo3D } from '../gg-3d-world';
|
|
3
3
|
export declare abstract class IRenderable3dEntity<TypeDoc extends VisualTypeDocRepo3D = VisualTypeDocRepo3D, PTypeDoc extends PhysicsTypeDocRepo3D = PhysicsTypeDocRepo3D> extends IRenderableEntity<Point3, Point4, TypeDoc, PTypeDoc> {
|
|
4
|
+
protected _world: Gg3dWorld<TypeDoc, PTypeDoc> | null;
|
|
5
|
+
get world(): Gg3dWorld<TypeDoc, PTypeDoc> | null;
|
|
4
6
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
|
2
|
-
import { Graph, IEntity, Point3, Point4, TickOrder } from '../../base';
|
|
2
|
+
import { Graph, IEntity, PausableClock, Point3, Point4, TickOrder } from '../../base';
|
|
3
3
|
import { Gg3dWorld, PhysicsTypeDocRepo3D, VisualTypeDocRepo3D } from '../gg-3d-world';
|
|
4
4
|
import { Entity3d } from './entity-3d';
|
|
5
5
|
import { LoadOptions, LoadResultWithProps } from '../loader';
|
|
@@ -60,10 +60,9 @@ export declare class MapGraph3dEntity<VTypeDoc extends VisualTypeDocRepo3D = Vis
|
|
|
60
60
|
rotation: Point4;
|
|
61
61
|
}
|
|
62
62
|
]>;
|
|
63
|
-
get world(): Gg3dWorld<VTypeDoc, PTypeDoc> | null;
|
|
64
|
-
protected _world: Gg3dWorld<VTypeDoc, PTypeDoc> | null;
|
|
65
63
|
protected readonly mapGraphNodes: MapGraph[];
|
|
66
64
|
protected readonly options: Gg3dMapGraphEntityOptions;
|
|
65
|
+
protected loadClock: PausableClock | null;
|
|
67
66
|
constructor(mapGraph: MapGraph, options?: Partial<Gg3dMapGraphEntityOptions>);
|
|
68
67
|
onSpawned(world: Gg3dWorld<VTypeDoc, PTypeDoc>): void;
|
|
69
68
|
onRemoved(): void;
|
|
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { BehaviorSubject, NEVER, startWith, Subject, switchMap, takeUntil } from 'rxjs';
|
|
11
|
-
import { distinctUntilChanged, map, tap
|
|
11
|
+
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
|
|
12
12
|
import { Graph, Qtrn, TickOrder } from '../../base';
|
|
13
13
|
import { IRenderable3dEntity } from './i-renderable-3d.entity';
|
|
14
14
|
export class MapGraph extends Graph {
|
|
@@ -90,7 +90,7 @@ export class MapGraph3dEntity extends IRenderable3dEntity {
|
|
|
90
90
|
this._initialLoadComplete$ = new BehaviorSubject(false);
|
|
91
91
|
this._nearestDummy$ = new BehaviorSubject(null);
|
|
92
92
|
this._chunkLoaded$ = new Subject();
|
|
93
|
-
this.
|
|
93
|
+
this.loadClock = null;
|
|
94
94
|
this.options = Object.assign(Object.assign({}, defaultOptions), options);
|
|
95
95
|
this.mapGraphNodes = mapGraph.nodes();
|
|
96
96
|
}
|
|
@@ -103,15 +103,14 @@ export class MapGraph3dEntity extends IRenderable3dEntity {
|
|
|
103
103
|
get chunkLoaded$() {
|
|
104
104
|
return this._chunkLoaded$.asObservable();
|
|
105
105
|
}
|
|
106
|
-
get world() {
|
|
107
|
-
return this._world;
|
|
108
|
-
}
|
|
109
106
|
onSpawned(world) {
|
|
110
107
|
super.onSpawned(world);
|
|
108
|
+
this.loadClock = world.createClock(true);
|
|
109
|
+
this.loadClock.tickRateLimit = 1;
|
|
111
110
|
this.loaderCursorEntity$
|
|
112
111
|
.pipe(takeUntil(this._onRemoved$), switchMap(entity => entity
|
|
113
|
-
? this.tick$.pipe(startWith(null), // map will perform initial loading even if world not started yet. Handy to preload map
|
|
114
|
-
takeUntil(entity.onRemoved$),
|
|
112
|
+
? this.loadClock.tick$.pipe(startWith(null), // map will perform initial loading even if world not started yet. Handy to preload map
|
|
113
|
+
takeUntil(entity.onRemoved$), map(() => entity.position))
|
|
115
114
|
: NEVER), map(pos => this.mapGraph.getNearestDummy(this.mapGraphNodes, pos)), tap(node => this._nearestDummy$.next(node)), distinctUntilChanged())
|
|
116
115
|
.subscribe(currentChunk => {
|
|
117
116
|
let haveToBeLoaded = new Set();
|
|
@@ -143,6 +142,10 @@ export class MapGraph3dEntity extends IRenderable3dEntity {
|
|
|
143
142
|
}
|
|
144
143
|
onRemoved() {
|
|
145
144
|
super.onRemoved();
|
|
145
|
+
if (this.loadClock) {
|
|
146
|
+
this.loadClock.stop();
|
|
147
|
+
this.loadClock = null;
|
|
148
|
+
}
|
|
146
149
|
this.loaderCursorEntity$.next(null);
|
|
147
150
|
}
|
|
148
151
|
loadChunk(node) {
|
|
@@ -51,6 +51,8 @@ export declare class RaycastVehicle3dEntity<VTypeDoc extends VisualTypeDocRepo3D
|
|
|
51
51
|
protected readonly wheelLocalRotation: (Point4 | null)[];
|
|
52
52
|
protected readonly frontWheelsIndices: number[];
|
|
53
53
|
protected readonly rearWheelsIndices: number[];
|
|
54
|
+
get name(): string;
|
|
55
|
+
set name(value: string);
|
|
54
56
|
getSpeed(): number;
|
|
55
57
|
readonly tractionWheelRadius: number;
|
|
56
58
|
private _steeringAngle;
|
|
@@ -93,11 +93,25 @@ export class RaycastVehicle3dEntity extends Entity3d {
|
|
|
93
93
|
localRotation = { x: 0, y: 0.707107 * (flip ? 1 : -1), z: 0, w: 0.707107 };
|
|
94
94
|
}
|
|
95
95
|
this.wheelLocalRotation.push(localRotation);
|
|
96
|
-
|
|
96
|
+
const wheelEntity = new Entity3d(entity);
|
|
97
|
+
wheelEntity.name = this.name + '__wheel_' + (options.isFront ? 'f' : 'r') + (options.isLeft ? 'l' : 'r');
|
|
98
|
+
this.wheels.push(wheelEntity);
|
|
97
99
|
}
|
|
98
100
|
this.addChildren(...this.wheels.filter(x => !!x));
|
|
99
101
|
this.vehicleComponent.entity = this;
|
|
100
102
|
}
|
|
103
|
+
get name() {
|
|
104
|
+
return super.name;
|
|
105
|
+
}
|
|
106
|
+
set name(value) {
|
|
107
|
+
const oldName = super.name;
|
|
108
|
+
super.name = value;
|
|
109
|
+
for (const w of this.wheels || []) {
|
|
110
|
+
if (w) {
|
|
111
|
+
w.name = w.name.replace(oldName, value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
101
115
|
// m/s
|
|
102
116
|
getSpeed() {
|
|
103
117
|
return this.vehicleComponent.wheelSpeed;
|
package/dist/3d/gg-3d-world.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { IClock } from './i-clock';
|
|
|
6
6
|
export declare class PausableClock implements IClock {
|
|
7
7
|
protected readonly parentClock: IClock;
|
|
8
8
|
private tickSub;
|
|
9
|
+
private readonly _internalTick$;
|
|
9
10
|
private readonly _tick$;
|
|
10
11
|
/**
|
|
11
12
|
* Observable stream of ticks, emitting an array containing the current time and the tick delta.
|
|
@@ -35,12 +36,17 @@ export declare class PausableClock implements IClock {
|
|
|
35
36
|
* Gets the elapsed time since the clock started.
|
|
36
37
|
*/
|
|
37
38
|
get elapsedTime(): number;
|
|
39
|
+
/**
|
|
40
|
+
* Tick rate limiter. If set to 0 - tick rate is unlimited, 15 means "allow at most 15 ticks per second"
|
|
41
|
+
*/
|
|
42
|
+
tickRateLimit: number;
|
|
38
43
|
private startedAt;
|
|
39
44
|
private oldRelativeTime;
|
|
40
45
|
private pausedAt;
|
|
41
46
|
private lastStopElapsed;
|
|
42
47
|
private _timeScale;
|
|
43
48
|
private pausedByTimescale;
|
|
49
|
+
private lastFiredTickElapsed;
|
|
44
50
|
/**
|
|
45
51
|
* Constructs a new PausableClock instance.
|
|
46
52
|
* @param autoStart Indicates whether the clock should start automatically upon creation.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Subject } from 'rxjs';
|
|
1
|
+
import { filter, Subject } from 'rxjs';
|
|
2
2
|
import { map, tap } from 'rxjs/operators';
|
|
3
3
|
import { GgGlobalClock } from './global-clock';
|
|
4
4
|
/**
|
|
@@ -13,7 +13,12 @@ export class PausableClock {
|
|
|
13
13
|
constructor(autoStart = false, parentClock = GgGlobalClock.instance) {
|
|
14
14
|
this.parentClock = parentClock;
|
|
15
15
|
this.tickSub = null;
|
|
16
|
+
this._internalTick$ = new Subject();
|
|
16
17
|
this._tick$ = new Subject();
|
|
18
|
+
/**
|
|
19
|
+
* Tick rate limiter. If set to 0 - tick rate is unlimited, 15 means "allow at most 15 ticks per second"
|
|
20
|
+
*/
|
|
21
|
+
this.tickRateLimit = 0;
|
|
17
22
|
// State variables
|
|
18
23
|
this.startedAt = -1;
|
|
19
24
|
this.oldRelativeTime = 0; // "elapsed", emitted on last tick
|
|
@@ -21,15 +26,21 @@ export class PausableClock {
|
|
|
21
26
|
this.lastStopElapsed = 0;
|
|
22
27
|
this._timeScale = 1;
|
|
23
28
|
this.pausedByTimescale = false;
|
|
29
|
+
this.lastFiredTickElapsed = 0;
|
|
24
30
|
if (autoStart) {
|
|
25
31
|
this.start();
|
|
26
32
|
}
|
|
33
|
+
this._internalTick$
|
|
34
|
+
.pipe(map(([_, newTime]) => [newTime, newTime - this.lastFiredTickElapsed]), filter(([elapsed]) => !this.tickRateLimit ||
|
|
35
|
+
Math.floor((this.lastFiredTickElapsed * this.tickRateLimit) / 1000) <
|
|
36
|
+
Math.floor((elapsed * this.tickRateLimit) / 1000)), tap(([elapsed]) => (this.lastFiredTickElapsed = elapsed)))
|
|
37
|
+
.subscribe(this._tick$);
|
|
27
38
|
}
|
|
28
39
|
/**
|
|
29
40
|
* Observable stream of ticks, emitting an array containing the current time and the tick delta.
|
|
30
41
|
*/
|
|
31
42
|
get tick$() {
|
|
32
|
-
return this._tick$.
|
|
43
|
+
return this._tick$.asObservable();
|
|
33
44
|
}
|
|
34
45
|
/**
|
|
35
46
|
* Checks if the clock is currently running.
|
|
@@ -145,7 +156,7 @@ export class PausableClock {
|
|
|
145
156
|
}
|
|
146
157
|
this.tickSub = this.parentClock.tick$
|
|
147
158
|
.pipe(map(([_, d]) => [this.oldRelativeTime, this.oldRelativeTime + d * this.timeScale]), tap(([_, cur]) => (this.oldRelativeTime = cur)))
|
|
148
|
-
.subscribe(this.
|
|
159
|
+
.subscribe(this._internalTick$);
|
|
149
160
|
}
|
|
150
161
|
/**
|
|
151
162
|
* Stops listening for ticks from the parent clock.
|
|
@@ -25,12 +25,12 @@ export declare abstract class IRendererComponent<D, R, VTypeDoc extends VisualTy
|
|
|
25
25
|
readonly canvas?: HTMLCanvasElement | undefined;
|
|
26
26
|
entity: IEntity | null;
|
|
27
27
|
/** Specifies the options for the renderer. */
|
|
28
|
-
readonly rendererOptions: RendererOptions
|
|
28
|
+
readonly rendererOptions: RendererOptions & Partial<VTypeDoc['rendererExtraOpts']>;
|
|
29
29
|
/** get flag whether renderer shows physics debugger view */
|
|
30
30
|
abstract get physicsDebugViewActive(): boolean;
|
|
31
31
|
/** turns on/off physics debugger view for this renderer */
|
|
32
32
|
abstract set physicsDebugViewActive(value: boolean);
|
|
33
|
-
protected constructor(scene: IVisualSceneComponent<D, R, VTypeDoc>, canvas?: HTMLCanvasElement | undefined, options?: Partial<RendererOptions>);
|
|
33
|
+
protected constructor(scene: IVisualSceneComponent<D, R, VTypeDoc>, canvas?: HTMLCanvasElement | undefined, options?: Partial<RendererOptions & VTypeDoc['rendererExtraOpts']>);
|
|
34
34
|
/**
|
|
35
35
|
* Renders the scene.
|
|
36
36
|
*/
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { GgWorld } from '../../gg-world';
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
|
-
export declare function createInlineTickController(world: GgWorld<any, any>, tickOrder?: number): Observable<[number, number]>;
|
|
3
|
+
export declare function createInlineTickController(world: GgWorld<any, any>, tickOrder?: number, name?: string): Observable<[number, number]>;
|
|
@@ -6,8 +6,11 @@ class InlineTickController extends IEntity {
|
|
|
6
6
|
this.tickOrder = tickOrder;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
export function createInlineTickController(world, tickOrder = TickOrder.CONTROLLERS) {
|
|
9
|
+
export function createInlineTickController(world, tickOrder = TickOrder.CONTROLLERS, name) {
|
|
10
10
|
const controller = new InlineTickController(tickOrder);
|
|
11
|
+
if (name) {
|
|
12
|
+
controller.name = name;
|
|
13
|
+
}
|
|
11
14
|
world.addEntity(controller);
|
|
12
15
|
return controller.tick$.pipe(finalize(() => {
|
|
13
16
|
world.removeEntity(controller, true);
|
package/dist/base/gg-world.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { IDisplayObjectComponent, IEntity, IPhysicsWorldComponent, IPositionable, IRenderableEntity, IRendererComponent, IRigidBodyComponent, ITriggerComponent, IVisualSceneComponent, KeyboardInput, PausableClock } from '../base';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
2
3
|
export declare type VisualTypeDocRepo<D, R> = {
|
|
3
4
|
factory: unknown;
|
|
4
5
|
displayObject: IDisplayObjectComponent<D, R>;
|
|
5
6
|
renderer: IRendererComponent<D, R>;
|
|
7
|
+
rendererExtraOpts: {};
|
|
6
8
|
};
|
|
7
9
|
export declare type PhysicsTypeDocRepo<D, R> = {
|
|
8
10
|
factory: unknown;
|
|
@@ -12,14 +14,17 @@ export declare type PhysicsTypeDocRepo<D, R> = {
|
|
|
12
14
|
export declare abstract class GgWorld<D, R, VTypeDoc extends VisualTypeDocRepo<D, R> = VisualTypeDocRepo<D, R>, PTypeDoc extends PhysicsTypeDocRepo<D, R> = PhysicsTypeDocRepo<D, R>, VS extends IVisualSceneComponent<D, R, VTypeDoc> = IVisualSceneComponent<D, R, VTypeDoc>, PW extends IPhysicsWorldComponent<D, R, PTypeDoc> = IPhysicsWorldComponent<D, R, PTypeDoc>> {
|
|
13
15
|
readonly visualScene: VS;
|
|
14
16
|
readonly physicsWorld: PW;
|
|
17
|
+
private static default_name_counter;
|
|
18
|
+
private static _documentWorlds;
|
|
19
|
+
static get documentWorlds(): GgWorld<any, any>[];
|
|
15
20
|
readonly worldClock: PausableClock;
|
|
16
21
|
readonly keyboardInput: KeyboardInput;
|
|
17
|
-
private static default_name_counter;
|
|
18
22
|
name: string;
|
|
19
23
|
readonly children: IEntity[];
|
|
20
24
|
protected readonly tickListeners: IEntity[];
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
readonly tickStarted$: Subject<void>;
|
|
26
|
+
readonly tickForwardTo$: Subject<IEntity | 'PHYSICS_WORLD'>;
|
|
27
|
+
readonly tickForwardedTo$: Subject<IEntity | 'PHYSICS_WORLD'>;
|
|
23
28
|
protected constructor(visualScene: VS, physicsWorld: PW);
|
|
24
29
|
init(): Promise<void>;
|
|
25
30
|
start(): void;
|
package/dist/base/gg-world.js
CHANGED
|
@@ -8,6 +8,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { GgGlobalClock, IRendererEntity, KeyboardInput, TickOrder, } from '../base';
|
|
11
|
+
import { lastValueFrom, Subject, take } from 'rxjs';
|
|
12
|
+
import { PerformanceMeterEntity } from '../dev';
|
|
11
13
|
export class GgWorld {
|
|
12
14
|
constructor(visualScene, physicsWorld) {
|
|
13
15
|
this.visualScene = visualScene;
|
|
@@ -18,17 +20,13 @@ export class GgWorld {
|
|
|
18
20
|
this.children = [];
|
|
19
21
|
// the same as children, but sorted by tick order
|
|
20
22
|
this.tickListeners = [];
|
|
23
|
+
// events
|
|
24
|
+
this.tickStarted$ = new Subject();
|
|
25
|
+
this.tickForwardTo$ = new Subject();
|
|
26
|
+
this.tickForwardedTo$ = new Subject();
|
|
21
27
|
GgWorld._documentWorlds.push(this);
|
|
22
28
|
this.keyboardInput.start();
|
|
23
29
|
if (window.ggstatic) {
|
|
24
|
-
window.ggstatic.registerConsoleCommand(this, 'show_debugger', (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
window.ggstatic.showDebugControls = ['1', 'true', '+'].includes(args[0]);
|
|
26
|
-
return '' + window.ggstatic.showDebugControls;
|
|
27
|
-
}), 'args: [0 or 1]; turn on/off debug panel. Default value is 0');
|
|
28
|
-
window.ggstatic.registerConsoleCommand(this, 'show_stats', (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
29
|
-
window.ggstatic.showStats = ['1', 'true', '+'].includes(args[0]);
|
|
30
|
-
return '' + window.ggstatic.showStats;
|
|
31
|
-
}), 'args: [0 or 1]; turn on/off stats. Default value is 0');
|
|
32
30
|
window.ggstatic.registerConsoleCommand(this, 'ph_timescale', (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
33
31
|
this.worldClock.timeScale = +args[0];
|
|
34
32
|
return JSON.stringify(this.worldClock.timeScale);
|
|
@@ -56,6 +54,40 @@ export class GgWorld {
|
|
|
56
54
|
return 'false';
|
|
57
55
|
}), 'args: [0 or 1] or [0 or 1, string]; turn on/off physics debug view. Second argument expects renderer ' +
|
|
58
56
|
'name, if not provided first renderer will be picked. Look up for renderer names using command "ls_renderers"');
|
|
57
|
+
window.ggstatic.registerConsoleCommand(this, 'performance_report', (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
let mode = 'avg';
|
|
59
|
+
let samples = 20;
|
|
60
|
+
for (let i = 0; i < 2; i++) {
|
|
61
|
+
if (['avg', 'peak'].includes(args[i])) {
|
|
62
|
+
mode = args[i];
|
|
63
|
+
}
|
|
64
|
+
else if (!isNaN(+args[i])) {
|
|
65
|
+
samples = +args[i];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const meter = new PerformanceMeterEntity(samples, 250);
|
|
69
|
+
this.addEntity(meter);
|
|
70
|
+
yield lastValueFrom(this.worldClock.tick$.pipe(take(samples)));
|
|
71
|
+
const report = mode === 'avg' ? meter.avgReport : meter.peakReport;
|
|
72
|
+
this.removeEntity(meter);
|
|
73
|
+
const renderItems = report.entries.map(([name, value]) => `<span style='color:lightgray;'>${name}:</span>` +
|
|
74
|
+
new Array(Math.max(0, 26 - name.length)).join(' ') +
|
|
75
|
+
`${value.toFixed(2)} ms` +
|
|
76
|
+
(mode === 'avg' ? ` (${((value * 100) / report.totalTime).toFixed(2)}%)` : ''));
|
|
77
|
+
let totalColor = 'lightgreen';
|
|
78
|
+
if (report.totalTime > 12) {
|
|
79
|
+
totalColor = report.totalTime < 16 ? 'yellow' : 'red';
|
|
80
|
+
}
|
|
81
|
+
const title = `${mode === 'avg' ? 'Average' : 'Peak'} Frame time`;
|
|
82
|
+
renderItems.unshift(title +
|
|
83
|
+
':' +
|
|
84
|
+
new Array(Math.max(0, 26 - title.length)).join(' ') +
|
|
85
|
+
`<span style='color:${totalColor};'>${report.totalTime.toFixed(2)} ms</span>`);
|
|
86
|
+
renderItems.unshift(`Performance report (${samples} samples)`);
|
|
87
|
+
return renderItems.join('\n');
|
|
88
|
+
}), 'args: [int, avg|peak]; [avg|peak, int]; [avg|peak]; [int] or []; measure how much time was spent per ' +
|
|
89
|
+
'entity in world. Arguments are samples amount (20 by default) and "peak" or "avg" choice, both arguments are ' +
|
|
90
|
+
'optional. "avg" report sorts entities by average time consumed, "peak" records highest value for each entity');
|
|
59
91
|
}
|
|
60
92
|
}
|
|
61
93
|
static get documentWorlds() {
|
|
@@ -64,24 +96,30 @@ export class GgWorld {
|
|
|
64
96
|
init() {
|
|
65
97
|
return __awaiter(this, void 0, void 0, function* () {
|
|
66
98
|
yield Promise.all([this.physicsWorld.init(), this.visualScene.init()]);
|
|
99
|
+
const forwardTick = (listener, elapsed, delta) => {
|
|
100
|
+
if (listener.active) {
|
|
101
|
+
this.tickForwardTo$.next(listener);
|
|
102
|
+
listener.tick$.next([elapsed, delta]);
|
|
103
|
+
this.tickForwardedTo$.next(listener);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
67
106
|
this.worldClock.tick$.subscribe(([elapsed, delta]) => {
|
|
107
|
+
this.tickStarted$.next();
|
|
68
108
|
let i = 0;
|
|
69
109
|
// emit tick to all entities with tick order < GGTickOrder.PHYSICS_SIMULATION
|
|
70
110
|
for (i; i < this.tickListeners.length; i++) {
|
|
71
111
|
if (this.tickListeners[i].tickOrder >= TickOrder.PHYSICS_SIMULATION) {
|
|
72
112
|
break;
|
|
73
113
|
}
|
|
74
|
-
|
|
75
|
-
this.tickListeners[i].tick$.next([elapsed, delta]);
|
|
76
|
-
}
|
|
114
|
+
forwardTick(this.tickListeners[i], elapsed, delta);
|
|
77
115
|
}
|
|
78
116
|
// run physics simulation
|
|
117
|
+
this.tickForwardTo$.next('PHYSICS_WORLD');
|
|
79
118
|
this.physicsWorld.simulate(delta);
|
|
119
|
+
this.tickForwardedTo$.next('PHYSICS_WORLD');
|
|
80
120
|
// emit tick to all remained entities
|
|
81
121
|
for (i; i < this.tickListeners.length; i++) {
|
|
82
|
-
|
|
83
|
-
this.tickListeners[i].tick$.next([elapsed, delta]);
|
|
84
|
-
}
|
|
122
|
+
forwardTick(this.tickListeners[i], elapsed, delta);
|
|
85
123
|
}
|
|
86
124
|
});
|
|
87
125
|
});
|
|
@@ -113,6 +151,9 @@ export class GgWorld {
|
|
|
113
151
|
}
|
|
114
152
|
this.worldClock.stop();
|
|
115
153
|
this.keyboardInput.stop();
|
|
154
|
+
this.tickStarted$.complete();
|
|
155
|
+
this.tickForwardTo$.complete();
|
|
156
|
+
this.tickForwardedTo$.complete();
|
|
116
157
|
for (let i = 0; i < this.children.length; i++) {
|
|
117
158
|
this.children[i].onRemoved();
|
|
118
159
|
this.children[i].dispose();
|
|
@@ -162,11 +162,16 @@ export class MouseInput extends IInput {
|
|
|
162
162
|
.pipe(takeUntil(this.stopped$))
|
|
163
163
|
.subscribe((event) => {
|
|
164
164
|
if (pointers.length === 0) {
|
|
165
|
-
|
|
166
|
-
this.options.canvas
|
|
165
|
+
try {
|
|
166
|
+
if (this.options.canvas) {
|
|
167
|
+
this.options.canvas.setPointerCapture(event.pointerId);
|
|
168
|
+
}
|
|
169
|
+
this._element.addEventListener('pointerup', onPointerUp);
|
|
170
|
+
this._element.addEventListener('pointercancel', onPointerUp);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
console.error(err);
|
|
167
174
|
}
|
|
168
|
-
this._element.addEventListener('pointerup', onPointerUp);
|
|
169
|
-
this._element.addEventListener('pointercancel', onPointerUp);
|
|
170
175
|
}
|
|
171
176
|
pointers.push(event);
|
|
172
177
|
if (event.pointerType === 'touch') {
|
|
@@ -10,6 +10,11 @@ export declare class GgDebuggerUI {
|
|
|
10
10
|
get showDebugControls(): boolean;
|
|
11
11
|
setShowDebugControls(selectedWorld: GgWorld<any, any>, value: boolean): void;
|
|
12
12
|
private snapshot;
|
|
13
|
+
perfStatsMode: 'AVG' | 'PEAK';
|
|
14
|
+
private performanceStatsSnapshot;
|
|
13
15
|
private makeSnapshot;
|
|
16
|
+
private makePerformanceStatsSnapshot;
|
|
17
|
+
css: string;
|
|
14
18
|
private renderControls;
|
|
19
|
+
private renderPerformanceStats;
|
|
15
20
|
}
|
|
@@ -2,9 +2,12 @@ import { createInlineTickController, IRendererEntity } from '../base';
|
|
|
2
2
|
import { animationFrameScheduler, fromEvent, of, Subject, takeUntil } from 'rxjs';
|
|
3
3
|
import Stats from 'stats.js';
|
|
4
4
|
import { repeat } from 'rxjs/operators';
|
|
5
|
+
import { PerformanceMeterEntity } from './performance-meter.entity';
|
|
5
6
|
const snapshotEqual = (a, b) => {
|
|
6
7
|
if (a.timeScale !== b.timeScale)
|
|
7
8
|
return false;
|
|
9
|
+
if (a.performanceStatsEnabled !== b.performanceStatsEnabled)
|
|
10
|
+
return false;
|
|
8
11
|
if (a.renderers.length !== b.renderers.length)
|
|
9
12
|
return false;
|
|
10
13
|
for (let i = 0; i < a.renderers.length; i++) {
|
|
@@ -17,6 +20,23 @@ const snapshotEqual = (a, b) => {
|
|
|
17
20
|
}
|
|
18
21
|
return true;
|
|
19
22
|
};
|
|
23
|
+
const performanceStatsSnapshotEqual = (a, b) => {
|
|
24
|
+
if (!!a !== !!b)
|
|
25
|
+
return false;
|
|
26
|
+
if (a && b) {
|
|
27
|
+
if (a.totalTime !== b.totalTime)
|
|
28
|
+
return false;
|
|
29
|
+
if (a.entries.length !== b.entries.length)
|
|
30
|
+
return false;
|
|
31
|
+
for (let i = 0; i < a.entries.length; i++) {
|
|
32
|
+
if (a.entries[i][0] !== b.entries[i][0])
|
|
33
|
+
return false;
|
|
34
|
+
if (a.entries[i][1] !== b.entries[i][1])
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
};
|
|
20
40
|
export class GgDebuggerUI {
|
|
21
41
|
constructor() {
|
|
22
42
|
this.ui = {
|
|
@@ -30,7 +50,11 @@ export class GgDebuggerUI {
|
|
|
30
50
|
this.snapshot = {
|
|
31
51
|
timeScale: 1,
|
|
32
52
|
renderers: [],
|
|
53
|
+
performanceStatsEnabled: false,
|
|
33
54
|
};
|
|
55
|
+
this.perfStatsMode = 'AVG';
|
|
56
|
+
this.performanceStatsSnapshot = null;
|
|
57
|
+
this.css = "style='display:flex;align-items:center;margin:0.25rem;'";
|
|
34
58
|
}
|
|
35
59
|
get showStats() {
|
|
36
60
|
return !!this.ui.stats;
|
|
@@ -52,12 +76,12 @@ export class GgDebuggerUI {
|
|
|
52
76
|
stats.dom.style.right = '0';
|
|
53
77
|
stats.showPanel(0); // 0: fps, 1: ms, 2: mb
|
|
54
78
|
document.body.appendChild(stats.dom);
|
|
55
|
-
createInlineTickController(selectedWorld, -1)
|
|
79
|
+
createInlineTickController(selectedWorld, -1, 'fps_meter_init')
|
|
56
80
|
.pipe(takeUntil(this.statsRemoved$))
|
|
57
81
|
.subscribe(() => {
|
|
58
82
|
stats === null || stats === void 0 ? void 0 : stats.begin();
|
|
59
83
|
});
|
|
60
|
-
createInlineTickController(selectedWorld, 10000)
|
|
84
|
+
createInlineTickController(selectedWorld, 10000, 'fps_meter')
|
|
61
85
|
.pipe(takeUntil(this.statsRemoved$))
|
|
62
86
|
.subscribe(() => {
|
|
63
87
|
stats === null || stats === void 0 ? void 0 : stats.end();
|
|
@@ -90,7 +114,9 @@ export class GgDebuggerUI {
|
|
|
90
114
|
debugControlsContainer.style.cssText =
|
|
91
115
|
'position:fixed;top:48px;right:0;opacity:0.9;z-index:9999;background-color:#333;color:white;display:flex;flex-direction:column';
|
|
92
116
|
this.snapshot = this.makeSnapshot();
|
|
117
|
+
this.performanceStatsSnapshot = this.makePerformanceStatsSnapshot();
|
|
93
118
|
this.renderControls(debugControlsContainer);
|
|
119
|
+
this.renderPerformanceStats();
|
|
94
120
|
of(undefined, animationFrameScheduler)
|
|
95
121
|
.pipe(repeat(), takeUntil(this.debugControlsRemoved$))
|
|
96
122
|
.subscribe(() => {
|
|
@@ -99,6 +125,11 @@ export class GgDebuggerUI {
|
|
|
99
125
|
this.snapshot = newSnapshot;
|
|
100
126
|
this.renderControls(debugControlsContainer);
|
|
101
127
|
}
|
|
128
|
+
const newPerformanceStats = this.makePerformanceStatsSnapshot();
|
|
129
|
+
if (!performanceStatsSnapshotEqual(this.performanceStatsSnapshot, newPerformanceStats)) {
|
|
130
|
+
this.performanceStatsSnapshot = newPerformanceStats;
|
|
131
|
+
this.renderPerformanceStats();
|
|
132
|
+
}
|
|
102
133
|
});
|
|
103
134
|
}
|
|
104
135
|
else {
|
|
@@ -108,33 +139,74 @@ export class GgDebuggerUI {
|
|
|
108
139
|
}
|
|
109
140
|
}
|
|
110
141
|
makeSnapshot() {
|
|
142
|
+
const renderers = [];
|
|
143
|
+
let performanceMeter = null;
|
|
144
|
+
for (const e of this.currentWorld.children) {
|
|
145
|
+
if (e instanceof IRendererEntity) {
|
|
146
|
+
renderers.push(e);
|
|
147
|
+
}
|
|
148
|
+
else if (e instanceof PerformanceMeterEntity) {
|
|
149
|
+
performanceMeter = e;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
111
152
|
return {
|
|
112
153
|
timeScale: this.currentWorld.worldClock.timeScale,
|
|
113
|
-
renderers:
|
|
114
|
-
.filter(x => x instanceof IRendererEntity)
|
|
115
|
-
.map(r => ({
|
|
154
|
+
renderers: renderers.map(r => ({
|
|
116
155
|
name: r.name,
|
|
117
156
|
entity: r,
|
|
118
157
|
physicsDebugViewActive: r.physicsDebugViewActive,
|
|
119
158
|
})),
|
|
159
|
+
performanceStatsEnabled: !!performanceMeter,
|
|
120
160
|
};
|
|
121
161
|
}
|
|
162
|
+
makePerformanceStatsSnapshot() {
|
|
163
|
+
if (!this.snapshot.performanceStatsEnabled)
|
|
164
|
+
return null;
|
|
165
|
+
let performanceMeter = this.currentWorld.children.find(e => e instanceof PerformanceMeterEntity);
|
|
166
|
+
if (performanceMeter) {
|
|
167
|
+
if (this.perfStatsMode === 'AVG') {
|
|
168
|
+
return performanceMeter.avgReport;
|
|
169
|
+
}
|
|
170
|
+
else if (this.perfStatsMode === 'PEAK') {
|
|
171
|
+
return performanceMeter.peakReport;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
122
176
|
renderControls(debugControlsContainer) {
|
|
123
|
-
const debugLabelCss = "style='display:flex;align-items:center;margin:0.25rem;'";
|
|
124
177
|
let html = '';
|
|
125
178
|
for (const { entity, physicsDebugViewActive } of this.snapshot.renderers) {
|
|
126
179
|
html += `
|
|
127
|
-
<div ${
|
|
180
|
+
<div ${this.css}>
|
|
128
181
|
<input type='checkbox' name='checkbox' id='physics_debugger_checkbox_id_${entity.name}' value='1'${physicsDebugViewActive ? ' checked' : ''}>
|
|
129
182
|
<label for='physics_debugger_checkbox_id_${entity.name}' style='user-select: none;'>Physics debugger${this.snapshot.renderers.length > 1 ? ' (' + entity.name + ')' : ''}</label>
|
|
130
183
|
</div>`;
|
|
131
184
|
}
|
|
132
185
|
html += `
|
|
133
|
-
<div ${
|
|
186
|
+
<div ${this.css}>
|
|
134
187
|
<input id='time_scale_slider' type='range' min='0' max='5' step='0.01' style='flex-grow:1' value='${this.snapshot.timeScale}'/>
|
|
135
188
|
<label for='time_scale_slider' style='user-select: none;'>Time scale</label>
|
|
136
189
|
</div>`;
|
|
190
|
+
html +=
|
|
191
|
+
`
|
|
192
|
+
<div ${this.css}>
|
|
193
|
+
<input type='checkbox' name='checkbox' id='perf_stats_checkbox_id' value='1'${this.snapshot.performanceStatsEnabled ? ' checked' : ''}>
|
|
194
|
+
<label for='perf_stats_checkbox_id' style='user-select: none;'>Entities performance distribution</label>
|
|
195
|
+
</div>
|
|
196
|
+
` +
|
|
197
|
+
(this.snapshot.performanceStatsEnabled
|
|
198
|
+
? `<div ${this.css}>
|
|
199
|
+
<label for='per_mode'>Mode:</label>
|
|
200
|
+
<select id='per_mode'>
|
|
201
|
+
<option value='AVG' ${this.perfStatsMode === 'AVG' ? 'selected' : ''}>Average</option>
|
|
202
|
+
<option value='PEAK' ${this.perfStatsMode === 'PEAK' ? 'selected' : ''}>Peak</option>
|
|
203
|
+
</select>
|
|
204
|
+
</div>`
|
|
205
|
+
: '') +
|
|
206
|
+
`
|
|
207
|
+
<div style='display: contents' id='perf_stats_container'></div>`;
|
|
137
208
|
debugControlsContainer.innerHTML = html;
|
|
209
|
+
debugControlsContainer.style.minWidth = '25rem';
|
|
138
210
|
this.viewUpdated$.next();
|
|
139
211
|
for (const { entity } of this.snapshot.renderers) {
|
|
140
212
|
fromEvent(document.getElementById('physics_debugger_checkbox_id_' + entity.name), 'change')
|
|
@@ -158,5 +230,54 @@ export class GgDebuggerUI {
|
|
|
158
230
|
console.error(err);
|
|
159
231
|
}
|
|
160
232
|
});
|
|
233
|
+
fromEvent(document.getElementById('perf_stats_checkbox_id'), 'change')
|
|
234
|
+
.pipe(takeUntil(this.debugControlsRemoved$), takeUntil(this.viewUpdated$))
|
|
235
|
+
.subscribe(e => {
|
|
236
|
+
const entity = this.currentWorld.children.find(x => x instanceof PerformanceMeterEntity);
|
|
237
|
+
if (e.target.checked == !entity) {
|
|
238
|
+
if (entity) {
|
|
239
|
+
this.currentWorld.removeEntity(entity);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
this.currentWorld.addEntity(new PerformanceMeterEntity());
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
if (this.snapshot.performanceStatsEnabled) {
|
|
247
|
+
fromEvent(document.getElementById('per_mode'), 'change')
|
|
248
|
+
.pipe(takeUntil(this.debugControlsRemoved$), takeUntil(this.viewUpdated$))
|
|
249
|
+
.subscribe(e => {
|
|
250
|
+
this.perfStatsMode = e.target.value;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
renderPerformanceStats() {
|
|
255
|
+
const pCss = "style='display:flex;align-items:center;margin:0.25rem;max-width:25rem'";
|
|
256
|
+
const em = document.getElementById('perf_stats_container');
|
|
257
|
+
if (!em)
|
|
258
|
+
return;
|
|
259
|
+
let html = '';
|
|
260
|
+
if (this.performanceStatsSnapshot) {
|
|
261
|
+
const total = this.performanceStatsSnapshot.totalTime;
|
|
262
|
+
const labelCss = `style='user-select:none;width:3.5rem;text-align:left;flex-shrink:0;'`;
|
|
263
|
+
const progressCss = `style='width:11.5rem;margin:4px;flex-shrink:0;'`;
|
|
264
|
+
const spanCss = `style='user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;width:calc(9.5rem - 8px);'`;
|
|
265
|
+
html += `
|
|
266
|
+
<div ${pCss}>
|
|
267
|
+
<label for='stat_progress' ${labelCss}>${total.toFixed(2)}ms</label>
|
|
268
|
+
<progress id="stat_progress" ${progressCss} max="16" value="${total}"></progress>
|
|
269
|
+
<span ${spanCss}>Total frame time</span>
|
|
270
|
+
</div>`;
|
|
271
|
+
for (const [i, [name, value]] of this.performanceStatsSnapshot.entries.entries()) {
|
|
272
|
+
const strVal = this.perfStatsMode == 'AVG' ? ((value * 100) / total).toFixed(2) + '%' : value.toFixed(2) + 'ms';
|
|
273
|
+
html += `
|
|
274
|
+
<div ${pCss}>
|
|
275
|
+
<label for='stat_progress_${i}' ${labelCss}>${strVal}</label>
|
|
276
|
+
<progress id='stat_progress_${i}' ${progressCss} max='${total}' value='${value}'></progress>
|
|
277
|
+
<span ${spanCss}>${name}</span>
|
|
278
|
+
</div>`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
em.innerHTML = html;
|
|
161
282
|
}
|
|
162
283
|
}
|
package/dist/dev/gg-static.js
CHANGED
|
@@ -30,13 +30,13 @@ export class GgStatic {
|
|
|
30
30
|
this.consoleCommands = new Map();
|
|
31
31
|
this.registerConsoleCommand(null, 'ls_commands', () => __awaiter(this, void 0, void 0, function* () {
|
|
32
32
|
return this.availableCommands
|
|
33
|
-
.map(([key, value]) => `<span style=
|
|
33
|
+
.map(([key, value]) => `<span style='color:yellow'>${key}</span>${value.doc ? '\t<span style="color:#aaa">// ' + value.doc + '</span>' : ''}`)
|
|
34
34
|
.sort()
|
|
35
35
|
.join('\n\n');
|
|
36
36
|
}), 'no args; print all available commands');
|
|
37
37
|
this.registerConsoleCommand(null, 'ls_worlds', () => __awaiter(this, void 0, void 0, function* () {
|
|
38
38
|
return GgWorld.documentWorlds
|
|
39
|
-
.map(w => (w === this.selectedWorld ? `<span style=
|
|
39
|
+
.map(w => (w === this.selectedWorld ? `<span style='color:lightgreen;'>* ${w.name}</span>` : ` ${w.name}`))
|
|
40
40
|
.join('\n');
|
|
41
41
|
}), 'no args; print all available worlds');
|
|
42
42
|
this.registerConsoleCommand(null, 'select_world', (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -49,6 +49,14 @@ export class GgStatic {
|
|
|
49
49
|
}
|
|
50
50
|
return ((_a = this.selectedWorld) === null || _a === void 0 ? void 0 : _a.name) || 'null';
|
|
51
51
|
}), 'args: [string]; select world by name');
|
|
52
|
+
this.registerConsoleCommand(null, 'show_debugger', (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
this.showDebugControls = ['1', 'true', '+'].includes(args[0]);
|
|
54
|
+
return '' + this.showDebugControls;
|
|
55
|
+
}), 'args: [0 or 1]; turn on/off debug panel. Default value is 0');
|
|
56
|
+
this.registerConsoleCommand(null, 'show_stats', (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
this.showStats = ['1', 'true', '+'].includes(args[0]);
|
|
58
|
+
return '' + this.showStats;
|
|
59
|
+
}), 'args: [0 or 1]; turn on/off stats. Default value is 0');
|
|
52
60
|
}
|
|
53
61
|
static get instance() {
|
|
54
62
|
if (!window.ggstatic) {
|
|
@@ -126,14 +134,14 @@ export class GgStatic {
|
|
|
126
134
|
if (!action) {
|
|
127
135
|
action = (this.consoleCommands.get(this.selectedWorld) || {})[command];
|
|
128
136
|
if (!action) {
|
|
129
|
-
return `<span style=
|
|
137
|
+
return `<span style='color:red'>Unrecognized command: ${command}</span>`;
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
try {
|
|
133
141
|
return yield action.handler(...args);
|
|
134
142
|
}
|
|
135
143
|
catch (err) {
|
|
136
|
-
return `<span style=
|
|
144
|
+
return `<span style='color:red'>${err}</span>`;
|
|
137
145
|
}
|
|
138
146
|
});
|
|
139
147
|
}
|
package/dist/dev/index.d.ts
CHANGED
package/dist/dev/index.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { GgWorld, IEntity, TickOrder } from '../base';
|
|
2
|
+
export declare class PerformanceMeterEntity extends IEntity {
|
|
3
|
+
private readonly maxSamples;
|
|
4
|
+
private readonly maxRows;
|
|
5
|
+
readonly tickOrder: TickOrder | number;
|
|
6
|
+
private sampleIndex;
|
|
7
|
+
private collectedData;
|
|
8
|
+
private _avgReport;
|
|
9
|
+
get avgReport(): {
|
|
10
|
+
totalTime: number;
|
|
11
|
+
entries: [string, number][];
|
|
12
|
+
};
|
|
13
|
+
private _peakReport;
|
|
14
|
+
get peakReport(): {
|
|
15
|
+
totalTime: number;
|
|
16
|
+
entries: [string, number][];
|
|
17
|
+
};
|
|
18
|
+
constructor(maxSamples?: number, maxRows?: number);
|
|
19
|
+
onSpawned(world: GgWorld<unknown, unknown>): void;
|
|
20
|
+
onRemoved(): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { IEntity } from '../base';
|
|
2
|
+
import { takeUntil } from 'rxjs';
|
|
3
|
+
export class PerformanceMeterEntity extends IEntity {
|
|
4
|
+
constructor(maxSamples = 60, maxRows = 15) {
|
|
5
|
+
super();
|
|
6
|
+
this.maxSamples = maxSamples;
|
|
7
|
+
this.maxRows = maxRows;
|
|
8
|
+
this.tickOrder = Number.MAX_SAFE_INTEGER;
|
|
9
|
+
this.sampleIndex = 0;
|
|
10
|
+
this.collectedData = new Map();
|
|
11
|
+
this._avgReport = { totalTime: 0, entries: [] };
|
|
12
|
+
this._peakReport = { totalTime: 0, entries: [] };
|
|
13
|
+
}
|
|
14
|
+
get avgReport() {
|
|
15
|
+
return this._avgReport;
|
|
16
|
+
}
|
|
17
|
+
get peakReport() {
|
|
18
|
+
return this._peakReport;
|
|
19
|
+
}
|
|
20
|
+
onSpawned(world) {
|
|
21
|
+
super.onSpawned(world);
|
|
22
|
+
this.sampleIndex = 0;
|
|
23
|
+
const now = () => (typeof performance === 'undefined' ? Date : performance).now();
|
|
24
|
+
this.world.tickForwardTo$.pipe(takeUntil(this.onRemoved$)).subscribe(e => {
|
|
25
|
+
if (e === this)
|
|
26
|
+
return;
|
|
27
|
+
if (!this.collectedData.has(e)) {
|
|
28
|
+
this.collectedData.set(e, [[this.sampleIndex, now(), 0]]);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
this.collectedData.get(e).push([this.sampleIndex, now(), 0]);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
this.world.tickForwardedTo$.pipe(takeUntil(this.onRemoved$)).subscribe(e => {
|
|
35
|
+
if (e === this)
|
|
36
|
+
return;
|
|
37
|
+
const report = this.collectedData.get(e);
|
|
38
|
+
report[report.length - 1][2] = now();
|
|
39
|
+
});
|
|
40
|
+
this.tick$.pipe(takeUntil(this.onRemoved$)).subscribe(() => {
|
|
41
|
+
this.sampleIndex++;
|
|
42
|
+
// remove oldest samples
|
|
43
|
+
const entries = Array.from(this.collectedData.entries());
|
|
44
|
+
for (const [k, v] of entries) {
|
|
45
|
+
while (v.length > 0 && v[0][0] < this.sampleIndex - this.maxSamples) {
|
|
46
|
+
v.shift();
|
|
47
|
+
}
|
|
48
|
+
if (v.length === 0) {
|
|
49
|
+
this.collectedData.delete(k);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// helper functions
|
|
53
|
+
const label = (e) => {
|
|
54
|
+
if (e instanceof IEntity) {
|
|
55
|
+
if (e.name === '') {
|
|
56
|
+
return e.constructor.name;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
return e.name;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (e === 'PHYSICS_WORLD') {
|
|
63
|
+
return 'Physics simulation';
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
return e;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const truncateReport = (report) => {
|
|
70
|
+
let restSum = 0;
|
|
71
|
+
let restAmount = 0;
|
|
72
|
+
for (restAmount; restAmount < report.length - 2; restAmount++) {
|
|
73
|
+
if (this.maxRows > report.length - restAmount + 1) {
|
|
74
|
+
restAmount--;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
restSum += report[report.length - restAmount - 1][1];
|
|
78
|
+
}
|
|
79
|
+
if (restAmount > 1) {
|
|
80
|
+
report = report.slice(0, report.length - restAmount);
|
|
81
|
+
report.push([`Rest (${restAmount})`, restSum]);
|
|
82
|
+
}
|
|
83
|
+
return report;
|
|
84
|
+
};
|
|
85
|
+
// build avg report
|
|
86
|
+
let totalTimeAvg = 0;
|
|
87
|
+
let avgReport = Array.from(this.collectedData.entries())
|
|
88
|
+
.map(([e, samples]) => {
|
|
89
|
+
const timeSpentAvg = samples.reduce((prev, [_, start, finish]) => prev + finish - start, 0) / this.maxSamples;
|
|
90
|
+
totalTimeAvg += timeSpentAvg;
|
|
91
|
+
return [label(e), timeSpentAvg];
|
|
92
|
+
})
|
|
93
|
+
.sort((a, b) => b[1] - a[1]);
|
|
94
|
+
avgReport = truncateReport(avgReport);
|
|
95
|
+
this._avgReport = { totalTime: totalTimeAvg, entries: avgReport };
|
|
96
|
+
// build peak report
|
|
97
|
+
let totalTimePeaks = new Map();
|
|
98
|
+
let peakReport = Array.from(this.collectedData.entries())
|
|
99
|
+
.map(([e, samples]) => {
|
|
100
|
+
let peakTime = 0;
|
|
101
|
+
for (const [sample, start, finish] of samples) {
|
|
102
|
+
const time = finish - start;
|
|
103
|
+
peakTime = Math.max(peakTime, time);
|
|
104
|
+
totalTimePeaks.set(sample, time + (totalTimePeaks.get(sample) || 0));
|
|
105
|
+
}
|
|
106
|
+
return [label(e), peakTime];
|
|
107
|
+
})
|
|
108
|
+
.sort((a, b) => b[1] - a[1]);
|
|
109
|
+
peakReport = truncateReport(peakReport);
|
|
110
|
+
let totalPeak = Array.from(totalTimePeaks.values()).reduce((prev, cur) => Math.max(prev, cur), 0);
|
|
111
|
+
this._peakReport = { totalTime: totalPeak, entries: peakReport };
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
onRemoved() {
|
|
115
|
+
super.onRemoved();
|
|
116
|
+
this.collectedData = new Map();
|
|
117
|
+
this._avgReport = { totalTime: 0, entries: [] };
|
|
118
|
+
this._peakReport = { totalTime: 0, entries: [] };
|
|
119
|
+
}
|
|
120
|
+
}
|