Merge pull request #998 from thecodingmachine/skiprender2

Skip "render" if nothing changed on screen (2)
This commit is contained in:
David Négrier 2021-05-07 15:44:08 +02:00 committed by GitHub
commit 5605e63e5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 855 additions and 636 deletions

View File

@ -544,6 +544,14 @@ input[type=range]:focus::-ms-fill-upper {
/* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */ /* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */
} }
.game-overlay + div {
pointer-events: none;
}
.game-overlay + div > div {
pointer-events: auto;
}
.game-overlay.active { .game-overlay.active {
display: flex; display: flex;
} }

View File

@ -30,7 +30,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"generic-type-guard": "^3.2.0", "generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0", "google-protobuf": "^3.13.0",
"phaser": "3.24.1", "phaser": "^3.54.0",
"phaser3-rex-plugins": "^1.1.42", "phaser3-rex-plugins": "^1.1.42",
"queue-typescript": "^1.0.1", "queue-typescript": "^1.0.1",
"quill": "1.3.6", "quill": "1.3.6",

View File

@ -0,0 +1,52 @@
import {ResizableScene} from "../Login/ResizableScene";
import GameObject = Phaser.GameObjects.GameObject;
import Events = Phaser.Scenes.Events;
import AnimationEvents = Phaser.Animations.Events;
/**
* A scene that can track its dirty/pristine state.
*/
export abstract class DirtyScene extends ResizableScene {
private isAlreadyTracking: boolean = false;
protected dirty:boolean = true;
private objectListChanged:boolean = true;
/**
* Track all objects added to the scene and adds a callback each time an animation is added.
* Whenever an object is added, removed, or when an animation is played, the dirty state is set to true.
*
* Note: this does not work with animations from sprites inside containers.
*/
protected trackDirtyAnims(): void {
if (this.isAlreadyTracking) {
return;
}
this.isAlreadyTracking = true;
const trackAnimationFunction = this.trackAnimation.bind(this);
this.events.on(Events.ADDED_TO_SCENE, (gameObject: GameObject) => {
this.objectListChanged = true;
gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
});
this.events.on(Events.REMOVED_FROM_SCENE, (gameObject: GameObject) => {
this.objectListChanged = true;
gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
});
this.events.on(Events.RENDER, () => {
this.objectListChanged = false;
});
}
private trackAnimation(): void {
this.objectListChanged = true;
}
public isDirty(): boolean {
return this.dirty || this.objectListChanged;
}
public onResize(ev: UIEvent): void {
this.objectListChanged = true;
}
}

View File

@ -0,0 +1,88 @@
const Events = Phaser.Core.Events;
/**
* A specialization of the main Phaser Game scene.
* It comes with an optimization to skip rendering.
*
* Beware, the "step" function might vary in future versions of Phaser.
*/
export class Game extends Phaser.Game {
public step(time: number, delta: number)
{
// @ts-ignore
if (this.pendingDestroy)
{
// @ts-ignore
return this.runDestroy();
}
const eventEmitter = this.events;
// Global Managers like Input and Sound update in the prestep
eventEmitter.emit(Events.PRE_STEP, time, delta);
// This is mostly meant for user-land code and plugins
eventEmitter.emit(Events.STEP, time, delta);
// Update the Scene Manager and all active Scenes
this.scene.update(time, delta);
// Our final event before rendering starts
eventEmitter.emit(Events.POST_STEP, time, delta);
// This "if" is the changed introduced by the new "Game" class to avoid rendering unnecessarily.
if (this.isDirty()) {
const renderer = this.renderer;
// Run the Pre-render (clearing the canvas, setting background colors, etc)
renderer.preRender();
eventEmitter.emit(Events.PRE_RENDER, renderer, time, delta);
// The main render loop. Iterates all Scenes and all Cameras in those scenes, rendering to the renderer instance.
this.scene.render(renderer);
// The Post-Render call. Tidies up loose end, takes snapshots, etc.
renderer.postRender();
// The final event before the step repeats. Your last chance to do anything to the canvas before it all starts again.
eventEmitter.emit(Events.POST_RENDER, renderer, time, delta);
} else {
// @ts-ignore
this.scene.isProcessing = false;
}
}
private isDirty(): boolean {
// Loop through the scenes in forward order
for (let i = 0; i < this.scene.scenes.length; i++)
{
const scene = this.scene.scenes[i];
const sys = scene.sys;
if (sys.settings.visible && sys.settings.status >= Phaser.Scenes.LOADING && sys.settings.status < Phaser.Scenes.SLEEPING)
{
// @ts-ignore
if(typeof scene.isDirty === 'function') {
// @ts-ignore
const isDirty = scene.isDirty() || scene.tweens.getAllTweens().length > 0;
if (isDirty) {
return true;
}
} else {
return true;
}
}
}
return false;
}
}

View File

@ -83,6 +83,9 @@ import DOMElement = Phaser.GameObjects.DOMElement;
import {Subscription} from "rxjs"; import {Subscription} from "rxjs";
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import RenderTexture = Phaser.GameObjects.RenderTexture;
import Tilemap = Phaser.Tilemaps.Tilemap;
import {DirtyScene} from "./DirtyScene";
import {TextUtils} from "../Components/TextUtils"; import {TextUtils} from "../Components/TextUtils";
import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager"; import {PinchManager} from "../UserInput/PinchManager";
@ -126,13 +129,13 @@ interface DeleteGroupEventInterface {
const defaultStartLayerName = 'start'; const defaultStartLayerName = 'start';
export class GameScene extends ResizableScene implements CenterListener { export class GameScene extends DirtyScene implements CenterListener {
Terrains : Array<Phaser.Tilemaps.Tileset>; Terrains : Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer!: CurrentGamerInterface; CurrentPlayer!: CurrentGamerInterface;
MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayers!: Phaser.Physics.Arcade.Group;
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>(); MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
Map!: Phaser.Tilemaps.Tilemap; Map!: Phaser.Tilemaps.Tilemap;
Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>; Layers!: Array<Phaser.Tilemaps.TilemapLayer>;
Objects!: Array<Phaser.Physics.Arcade.Sprite>; Objects!: Array<Phaser.Physics.Arcade.Sprite>;
mapFile!: ITiledMap; mapFile!: ITiledMap;
groups: Map<number, Sprite>; groups: Map<number, Sprite>;
@ -366,6 +369,8 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook create scene //hook create scene
create(): void { create(): void {
this.trackDirtyAnims();
gameManager.gameSceneIsCreated(this); gameManager.gameSceneIsCreated(this);
urlManager.pushRoomIdToUrl(this.room); urlManager.pushRoomIdToUrl(this.room);
this.startLayerName = urlManager.getStartLayerNameFromUrl(); this.startLayerName = urlManager.getStartLayerNameFromUrl();
@ -396,12 +401,11 @@ export class GameScene extends ResizableScene implements CenterListener {
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels); this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
//add layer on map //add layer on map
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>(); this.Layers = new Array<Phaser.Tilemaps.TilemapLayer>();
let depth = -2; let depth = -2;
for (const layer of this.gameMap.layersIterator) { for (const layer of this.gameMap.layersIterator) {
if (layer.type === 'tilelayer') { if (layer.type === 'tilelayer') {
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth)); this.addLayer(this.Map.createLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
const exitSceneUrl = this.getExitSceneUrl(layer); const exitSceneUrl = this.getExitSceneUrl(layer);
if (exitSceneUrl !== undefined) { if (exitSceneUrl !== undefined) {
@ -1067,13 +1071,14 @@ ${escapedMessage}
this.updateCameraOffset(); this.updateCameraOffset();
} }
addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){ addLayer(Layer : Phaser.Tilemaps.TilemapLayer){
this.Layers.push(Layer); this.Layers.push(Layer);
} }
createCollisionWithPlayer() { createCollisionWithPlayer() {
this.physics.disableUpdate();
//add collision layer //add collision layer
this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => { this.Layers.forEach((Layer: Phaser.Tilemaps.TilemapLayer) => {
this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => { this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
}); });
@ -1202,12 +1207,24 @@ ${escapedMessage}
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. * @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
*/ */
update(time: number, delta: number) : void { update(time: number, delta: number) : void {
this.dirty = false;
mediaManager.updateScene(); mediaManager.updateScene();
this.currentTick = time; this.currentTick = time;
if (this.CurrentPlayer.isMoving()) {
this.dirty = true;
}
this.CurrentPlayer.moveUser(delta); this.CurrentPlayer.moveUser(delta);
if (this.CurrentPlayer.isMoving()) {
this.dirty = true;
this.physics.enableUpdate();
} else {
this.physics.disableUpdate();
}
// Let's handle all events // Let's handle all events
while (this.pendingEvents.length !== 0) { while (this.pendingEvents.length !== 0) {
this.dirty = true;
const event = this.pendingEvents.dequeue(); const event = this.pendingEvents.dequeue();
switch (event.type) { switch (event.type) {
case "InitUserPositionEvent": case "InitUserPositionEvent":
@ -1233,6 +1250,7 @@ ${escapedMessage}
// Let's move all users // Let's move all users
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time); const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => { updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => {
this.dirty = true;
const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId); const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
if (player === undefined) { if (player === undefined) {
throw new Error('Cannot find player with ID "' + userId + '"'); throw new Error('Cannot find player with ID "' + userId + '"');
@ -1402,7 +1420,8 @@ ${escapedMessage}
this.connection?.emitActionableEvent(itemId, eventName, state, parameters); this.connection?.emitActionableEvent(itemId, eventName, state, parameters);
} }
public onResize(): void { public onResize(ev: UIEvent): void {
super.onResize(ev);
this.reposition(); this.reposition();
// Send new viewport to server // Send new viewport to server

View File

@ -1,14 +1,14 @@
import {mediaManager} from "../../WebRtc/MediaManager"; import {mediaManager} from "../../WebRtc/MediaManager";
import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {localUserStore} from "../../Connexion/LocalUserStore"; import {localUserStore} from "../../Connexion/LocalUserStore";
import {ResizableScene} from "../Login/ResizableScene"; import {DirtyScene} from "../Game/DirtyScene";
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene'; export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
const helpCameraSettings = 'helpCameraSettings'; const helpCameraSettings = 'helpCameraSettings';
/** /**
* The scene that show how to permit Camera and Microphone access if there are not already allowed * The scene that show how to permit Camera and Microphone access if there are not already allowed
*/ */
export class HelpCameraSettingsScene extends ResizableScene { export class HelpCameraSettingsScene extends DirtyScene {
private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement; private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
private helpCameraSettingsOpened: boolean = false; private helpCameraSettingsOpened: boolean = false;
@ -73,6 +73,8 @@ export class HelpCameraSettingsScene extends ResizableScene {
ease: 'Power3', ease: 'Power3',
overflow: 'scroll' overflow: 'scroll'
}); });
this.dirty = true;
} }
private closeHelpCameraSettingsOpened(): void{ private closeHelpCameraSettingsOpened(): void{
@ -89,6 +91,8 @@ export class HelpCameraSettingsScene extends ResizableScene {
ease: 'Power3', ease: 'Power3',
overflow: 'scroll' overflow: 'scroll'
}); });
this.dirty = true;
} }
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) { private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
@ -100,20 +104,11 @@ export class HelpCameraSettingsScene extends ResizableScene {
} }
update(time: number, delta: number): void { update(time: number, delta: number): void {
if(this.helpCameraSettingsOpened){ this.dirty = false;
const middleX = this.getMiddleX();
const middleY = this.getMiddleY();
this.tweens.add({
targets: this.helpCameraSettingsElement,
x: middleX,
y: middleY,
duration: 1000,
ease: 'Power3'
});
}
} }
public onResize(ev: UIEvent): void { public onResize(ev: UIEvent): void {
super.onResize(ev);
const middleX = this.getMiddleX(); const middleX = this.getMiddleX();
const middleY = this.getMiddleY(); const middleY = this.getMiddleY();
this.tweens.add({ this.tweens.add({
@ -145,5 +140,8 @@ export class HelpCameraSettingsScene extends ResizableScene {
return (middleY > 0 ? middleY : 0); return (middleY > 0 ? middleY : 0);
} }
public isDirty(): boolean {
return this.dirty;
}
} }

View File

@ -350,4 +350,8 @@ export class MenuScene extends Phaser.Scene {
} }
} }
} }
public isDirty(): boolean {
return false;
}
} }

View File

@ -7,6 +7,7 @@ export const hasMovedEventName = "hasMoved";
export interface CurrentGamerInterface extends Character{ export interface CurrentGamerInterface extends Character{
moveUser(delta: number) : void; moveUser(delta: number) : void;
say(text : string) : void; say(text : string) : void;
isMoving(): boolean;
} }
export class Player extends Character implements CurrentGamerInterface { export class Player extends Character implements CurrentGamerInterface {
@ -83,4 +84,8 @@ export class Player extends Character implements CurrentGamerInterface {
} }
this.wasMoving = moving; this.wasMoving = moving;
} }
public isMoving(): boolean {
return this.wasMoving;
}
} }

View File

@ -1,4 +1,4 @@
export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline { export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline {
// the unique id of this pipeline // the unique id of this pipeline
public static readonly KEY = 'Outline'; public static readonly KEY = 'Outline';

View File

@ -22,7 +22,7 @@ export class ScreenSharingPeer extends Peer {
constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
reconnectTimer: 10000, //reconnectTimer: 10000,
config: { config: {
iceServers: [ iceServers: [
{ {

View File

@ -28,7 +28,7 @@ export class VideoPeer extends Peer {
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
reconnectTimer: 10000, //reconnectTimer: 10000,
config: { config: {
iceServers: [ iceServers: [
{ {

View File

@ -20,6 +20,7 @@ import {iframeListener} from "./Api/IframeListener";
import { SelectCharacterMobileScene } from './Phaser/Login/SelectCharacterMobileScene'; import { SelectCharacterMobileScene } from './Phaser/Login/SelectCharacterMobileScene';
import {HdpiManager} from "./Phaser/Services/HdpiManager"; import {HdpiManager} from "./Phaser/Services/HdpiManager";
import {waScaleManager} from "./Phaser/Services/WaScaleManager"; import {waScaleManager} from "./Phaser/Services/WaScaleManager";
import {Game} from "./Phaser/Game/Game";
const {width, height} = coWebsiteManager.getGameSize(); const {width, height} = coWebsiteManager.getGameSize();
@ -110,6 +111,8 @@ const config: GameConfig = {
debug: DEBUG_MODE, debug: DEBUG_MODE,
} }
}, },
// Instruct systems with 2 GPU to choose the low power one. We don't need that extra power and we want to save battery
powerPreference: "low-power",
callbacks: { callbacks: {
postBoot: game => { postBoot: game => {
// Commented out to try to fix MacOS bug // Commented out to try to fix MacOS bug
@ -121,7 +124,8 @@ const config: GameConfig = {
} }
}; };
const game = new Phaser.Game(config); //const game = new Phaser.Game(config);
const game = new Game(config);
waScaleManager.setScaleManager(game.scale); waScaleManager.setScaleManager(game.scale);

File diff suppressed because it is too large Load Diff

82
maps/tests/energy.json Normal file
View File

@ -0,0 +1,82 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"height":10,
"id":1,
"name":"floor",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":2,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":3,
"name":"floorLayer",
"objects":[
{
"height":261.73266830836,
"id":3,
"name":"",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":11,
"text":"Test:\nOpen this page in Chrome.\nOpen Chrome Task Manager (Shift - Esc)\n\nResult:\nThe WorkAdventure tab should take about 10% of CPU.\nThe GPU should almost consume nothing (~4%)",
"wrap":true
},
"type":"",
"visible":true,
"width":252.4375,
"x":46.5894222943362,
"y":34.2876372135732
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":8,
"nextobjectid":5,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"2021.03.23",
"tileheight":32,
"tilesets":[
{
"columns":11,
"firstgid":1,
"image":"tileset1.png",
"imageheight":352,
"imagewidth":352,
"margin":0,
"name":"tileset1",
"spacing":0,
"tilecount":121,
"tileheight":32,
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.5,
"width":10
}

View File

@ -66,6 +66,14 @@
<a href="#" class="testLink" data-testmap="mobile.json" target="_blank">Testing movement on mobile</a> <a href="#" class="testLink" data-testmap="mobile.json" target="_blank">Testing movement on mobile</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-energy"> Success <input type="radio" name="test-energy"> Failure <input type="radio" name="test-energy" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="energy.json" target="_blank">Test energy consumption</a>
</td>
</tr>
</table> </table>
<script> <script>