Major performance improvements

This commit is contained in:
tobspr 2020-05-18 17:40:20 +02:00
parent 260ba892c8
commit 2c48cb72aa
18 changed files with 194 additions and 60 deletions

View File

@ -7,4 +7,5 @@
display: flex;
line-height: 15px;
flex-direction: column;
color: #fff;
}

View File

@ -92,7 +92,7 @@ body.uiHidden {
body.modalDialogActive,
body.ingameDialogOpen {
> *:not(.ingameDialog):not(.modalDialogParent) {
> *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog) {
filter: blur(5px) !important;
}
}

View File

@ -71,7 +71,7 @@ export const globalConfig = {
debug: {
/* dev:start */
fastGameEnter: true,
// fastGameEnter: true,
noArtificialDelays: true,
// disableSavegameWrite: true,
showEntityBounds: false,
@ -80,11 +80,13 @@ export const globalConfig = {
disableMusic: true,
doNotRenderStatics: false,
disableZoomLimits: false,
showChunkBorders: false,
// showChunkBorders: true,
rewardsInstant: false,
allBuildingsUnlocked: true,
upgradesNoCost: true,
disableUnlockDialog: true,
// disableLogicTicks: true,
// testClipping: true,
// framePausesBetweenTicks: 40,
// testTranslations: true,
/* dev:end */

View File

@ -1,4 +1,5 @@
import { Rectangle } from "./rectangle";
import { globalConfig } from "./config";
/* typehints:start */
import { GameRoot } from "../game/root";
@ -21,5 +22,9 @@ export class DrawParameters {
// FIXME: Not really nice
/** @type {GameRoot} */
this.root = root;
if (G_IS_DEV && globalConfig.debug.testClipping) {
this.visibleRect = this.visibleRect.expandedInAllDirections(-100);
}
}
}

View File

@ -145,6 +145,57 @@ export class StaticMapEntityComponent extends Component {
return this.unapplyRotationToVector(localUnrotated);
}
/**
* Returns whether the entity should be drawn for the given parameters
* @param {DrawParameters} parameters
*/
shouldBeDrawn(parameters) {
let x = 0;
let y = 0;
let w = 0;
let h = 0;
switch (this.rotation) {
case 0: {
x = this.origin.x;
y = this.origin.y;
w = this.tileSize.x;
h = this.tileSize.y;
break;
}
case 90: {
x = this.origin.x - this.tileSize.y + 1;
y = this.origin.y;
w = this.tileSize.y;
h = this.tileSize.x;
break;
}
case 180: {
x = this.origin.x - this.tileSize.x + 1;
y = this.origin.y - this.tileSize.y + 1;
w = this.tileSize.x;
h = this.tileSize.y;
break;
}
case 270: {
x = this.origin.x;
y = this.origin.y - this.tileSize.x + 1;
w = this.tileSize.y;
h = this.tileSize.x;
break;
}
default:
assert(false, "Invalid rotation");
}
return parameters.visibleRect.containsRect4Params(
x * globalConfig.tileSize,
y * globalConfig.tileSize,
w * globalConfig.tileSize,
h * globalConfig.tileSize
);
}
/**
* Draws a sprite over the whole space of the entity
* @param {DrawParameters} parameters
@ -156,6 +207,10 @@ export class StaticMapEntityComponent extends Component {
const worldX = this.origin.x * globalConfig.tileSize;
const worldY = this.origin.y * globalConfig.tileSize;
if (!this.shouldBeDrawn(parameters)) {
return;
}
if (this.rotation === 0) {
// Early out, is faster
sprite.drawCached(
@ -164,7 +219,7 @@ export class StaticMapEntityComponent extends Component {
worldY - extrudePixels * this.tileSize.y,
globalConfig.tileSize * this.tileSize.x + 2 * extrudePixels * this.tileSize.x,
globalConfig.tileSize * this.tileSize.y + 2 * extrudePixels * this.tileSize.y,
clipping
false
);
} else {
const rotationCenterX = worldX + globalConfig.halfTileSize;

View File

@ -274,6 +274,11 @@ export class GameCore {
root.dynamicTickrate.beginTick();
if (G_IS_DEV && globalConfig.debug.disableLogicTicks) {
root.dynamicTickrate.endTick();
return true;
}
this.duringLogicUpdate = true;
// Update entities, this removes destroyed entities
@ -327,6 +332,8 @@ export class GameCore {
return;
}
this.root.dynamicTickrate.onFrameRendered();
if (!this.shouldRender()) {
// Always update hud tho
root.hud.update();
@ -341,6 +348,9 @@ export class GameCore {
// Gather context and save all state
const context = root.context;
context.save();
if (G_IS_DEV && globalConfig.debug.testClipping) {
context.clearRect(0, 0, window.innerWidth * 3, window.innerHeight * 3);
}
// Compute optimal zoom level and atlas scale
const zoomLevel = root.camera.zoomLevel;
@ -383,7 +393,6 @@ export class GameCore {
// -----
root.map.drawBackground(params);
// systems.mapResources.draw(params);
if (!this.root.camera.getIsMapOverlayActive()) {
systems.itemAcceptor.drawUnderlays(params);

View File

@ -6,6 +6,8 @@ import { round3Digits } from "../core/utils";
const logger = createLogger("dynamic_tickrate");
const fpsAccumulationTime = 1000;
export class DynamicTickrate {
/**
*
@ -18,9 +20,27 @@ export class DynamicTickrate {
this.capturedTicks = [];
this.averageTickDuration = 0;
this.accumulatedFps = 0;
this.accumulatedFpsLastUpdate = 0;
this.averageFps = 60;
this.setTickRate(60);
}
onFrameRendered() {
++this.accumulatedFps;
const now = performanceNow();
const timeDuration = now - this.accumulatedFpsLastUpdate;
if (timeDuration > fpsAccumulationTime) {
const avgFps = (this.accumulatedFps / fpsAccumulationTime) * 1000;
this.averageFps = avgFps;
this.accumulatedFps = 0;
this.accumulatedFpsLastUpdate = now;
}
}
/**
* Sets the tick rate to N updates per second
* @param {number} rate
@ -36,14 +56,16 @@ export class DynamicTickrate {
* Increases the tick rate marginally
*/
increaseTickRate() {
this.setTickRate(Math_round(Math_min(globalConfig.maximumTickRate, this.currentTickRate * 1.2)));
const desiredFps = this.root.app.settings.getDesiredFps();
this.setTickRate(Math_round(Math_min(desiredFps, this.currentTickRate * 1.2)));
}
/**
* Decreases the tick rate marginally
*/
decreaseTickRate() {
this.setTickRate(Math_round(Math_max(globalConfig.minimumTickRate, this.currentTickRate * 0.8)));
const desiredFps = this.root.app.settings.getDesiredFps();
this.setTickRate(Math_round(Math_max(desiredFps / 2, this.currentTickRate * 0.8)));
}
/**
@ -65,22 +87,14 @@ export class DynamicTickrate {
}
average /= this.capturedTicks.length;
// Calculate tick duration to cover X% of the frame
const ticksPerFrame = this.currentTickRate / 60;
const maxFrameDurationMs = 8;
const maxTickDuration = maxFrameDurationMs / ticksPerFrame;
// const maxTickDuration = (1000 / this.currentTickRate) * 0.75;
logger.log(
"Average time per tick:",
round3Digits(average) + "ms",
"allowed are",
maxTickDuration
);
this.averageTickDuration = average;
if (average < maxTickDuration) {
const desiredFps = this.root.app.settings.getDesiredFps();
if (this.averageFps > desiredFps * 0.9) {
// if (average < maxTickDuration) {
this.increaseTickRate();
} else {
} else if (this.averageFps < desiredFps * 0.7) {
this.decreaseTickRate();
}

View File

@ -1,19 +1,33 @@
import { BaseHUDPart } from "../base_hud_part";
import { makeDiv, round3Digits } from "../../../core/utils";
import { makeDiv, round3Digits, round2Digits } from "../../../core/utils";
import { Math_round } from "../../../core/builtins";
export class HUDDebugInfo extends BaseHUDPart {
createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_DebugInfo", []);
this.tickRateElement = makeDiv(this.element, null, ["tickRate"], "Ticks /s: 120");
this.fpsElement = makeDiv(this.element, null, ["fps"], "FPS: 60");
this.tickDurationElement = makeDiv(this.element, null, ["tickDuration"], "Update time: 0.5ms");
}
initialize() {}
initialize() {
this.lastTick = 0;
}
update() {
this.tickRateElement.innerText = "Tickrate: " + this.root.dynamicTickrate.currentTickRate;
this.tickDurationElement.innerText =
"Avg. Dur: " + round3Digits(this.root.dynamicTickrate.averageTickDuration) + "ms";
const now = this.root.time.realtimeNow();
if (now - this.lastTick > 0.25) {
this.lastTick = now;
this.tickRateElement.innerText = "Tickrate: " + this.root.dynamicTickrate.currentTickRate;
this.fpsElement.innerText =
"FPS: " +
Math_round(this.root.dynamicTickrate.averageFps) +
" (" +
round2Digits(1000 / this.root.dynamicTickrate.averageFps) +
" ms)";
this.tickDurationElement.innerText =
"Tick Dur: " + round3Digits(this.root.dynamicTickrate.averageTickDuration) + "ms";
}
}
}

View File

@ -243,6 +243,7 @@ export class MapChunk {
// Determine how likely it is that there is a color patch
const colorPatchChance = 0.9 - clamp(distanceToOriginInChunks / 25, 0, 1) * 0.5;
if (rng.next() < colorPatchChance) {
const colorPatchSize = Math_max(2, Math_round(1 + clamp(distanceToOriginInChunks / 8, 0, 4)));
this.internalGenerateColorPatch(rng, colorPatchSize, distanceToOriginInChunks);

View File

@ -149,19 +149,7 @@ export class MapChunkView extends MapChunk {
-this.tileX * globalConfig.tileSize,
-this.tileY * globalConfig.tileSize
);
// parameters.context.save();
// parameters.context.transform(
// 1,
// 0,
// 0,
// zoomLevel,
// this.tileX * globalConfig.tileSize,
// this.tileY * globalConfig.tileSize
// );
this.internalDrawBackgroundSystems(parameters);
// parameters.context.restore();
}
/**
@ -187,24 +175,11 @@ export class MapChunkView extends MapChunk {
zoomLevel,
root: this.root,
});
// parameters.context.save();
// parameters.context.save();
// parameters.context.transform(
// zoomLevel,
// 0,
// 0,
// zoomLevel,
// this.tileX * globalConfig.tileSize,
// this.tileY * globalConfig.tileSize
// );
parameters.context.translate(
-this.tileX * globalConfig.tileSize,
-this.tileY * globalConfig.tileSize
);
this.internalDrawForegroundSystems(parameters);
// parameters.context.restore();
}
/**

View File

@ -331,6 +331,10 @@ export class BeltSystem extends GameSystemWithFilter {
return;
}
if (!staticComp.shouldBeDrawn(parameters)) {
return;
}
for (let i = 0; i < items.length; ++i) {
const itemAndProgress = items[i];

View File

@ -41,6 +41,10 @@ export class HubSystem extends GameSystemWithFilter {
const context = parameters.context;
const staticComp = entity.components.StaticMapEntity;
if (!staticComp.shouldBeDrawn(parameters)) {
return;
}
const pos = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
// Background

View File

@ -59,6 +59,10 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
const staticComp = entity.components.StaticMapEntity;
const acceptorComp = entity.components.ItemAcceptor;
if (!staticComp.shouldBeDrawn(parameters)) {
return;
}
for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
animIndex
@ -88,6 +92,10 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
const staticComp = entity.components.StaticMapEntity;
const acceptorComp = entity.components.ItemAcceptor;
if (!staticComp.shouldBeDrawn(parameters)) {
return;
}
const underlays = acceptorComp.beltUnderlays;
for (let i = 0; i < underlays.length; ++i) {
const { pos, direction } = underlays[i];

View File

@ -141,6 +141,10 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
const ejectorComp = entity.components.ItemEjector;
const staticComp = entity.components.StaticMapEntity;
if (!staticComp.shouldBeDrawn(parameters)) {
return;
}
for (let i = 0; i < ejectorComp.slots.length; ++i) {
const slot = ejectorComp.slots[i];
const ejectedItem = slot.item;

View File

@ -13,23 +13,34 @@ export class MapResourcesSystem extends GameSystem {
const renderItems = parameters.zoomLevel >= globalConfig.mapChunkOverviewMinZoom;
parameters.context.globalAlpha = 0.5;
const layer = chunk.lowerLayer;
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
const row = layer[x];
const worldX = (chunk.tileX + x) * globalConfig.tileSize;
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
const lowerItem = row[y];
if (lowerItem) {
const worldY = (chunk.tileY + y) * globalConfig.tileSize;
if (
!parameters.visibleRect.containsRect4Params(
worldX,
worldY,
globalConfig.tileSize,
globalConfig.tileSize
)
) {
// Clipped
continue;
}
parameters.context.fillStyle = lowerItem.getBackgroundColorAsResource();
parameters.context.fillRect(
(chunk.tileX + x) * globalConfig.tileSize,
(chunk.tileY + y) * globalConfig.tileSize,
globalConfig.tileSize,
globalConfig.tileSize
);
parameters.context.fillRect(worldX, worldY, globalConfig.tileSize, globalConfig.tileSize);
if (renderItems) {
lowerItem.draw(
(chunk.tileX + x + 0.5) * globalConfig.tileSize,
(chunk.tileY + y + 0.5) * globalConfig.tileSize,
worldX + globalConfig.halfTileSize,
worldY + globalConfig.halfTileSize,
parameters
);
}

View File

@ -103,6 +103,10 @@ export class MinerSystem extends GameSystemWithFilter {
if (entity && entity.components.Miner) {
const staticComp = entity.components.StaticMapEntity;
if (!staticComp.shouldBeDrawn(parameters)) {
return;
}
const lowerLayerItem = this.root.map.getLowerLayerContentXY(
staticComp.origin.x,
staticComp.origin.y

View File

@ -102,6 +102,19 @@ export const allApplicationSettings = [
document.body.setAttribute("data-theme", id);
},
}),
new EnumSetting("refreshRate", {
options: ["60", "100", "144", "165"],
valueGetter: rate => rate,
textGetter: rate => rate + " Hz",
category: categoryGame,
restartRequired: false,
changeCb:
/**
* @param {Application} app
*/
(app, id) => {},
}),
];
export function getApplicationSettingById(id) {
@ -116,6 +129,7 @@ class SettingsStorage {
this.soundsMuted = false;
this.musicMuted = false;
this.theme = "light";
this.refreshRate = "60";
}
}
@ -168,6 +182,10 @@ export class ApplicationSettings extends ReadWriteProxy {
return this.getAllSettings().uiScale;
}
getDesiredFps() {
return parseInt(this.getAllSettings().refreshRate);
}
getInterfaceScaleValue() {
const id = this.getInterfaceScaleId();
for (let i = 0; i < uiScales.length; ++i) {
@ -234,7 +252,7 @@ export class ApplicationSettings extends ReadWriteProxy {
}
getCurrentVersion() {
return 3;
return 4;
}
migrate(data) {

View File

@ -73,7 +73,7 @@ mainMenu:
# This is shown when using firefox and other browsers which are not supported.
browserWarning: >-
This game is optimized for Google Chrome. Your browser is not supported or slow!
Sorry, but the game is known to run slow on your browser! Get the steam version or download chrome for the full experience.
dialogs:
buttons:
@ -325,3 +325,8 @@ settings:
title: Game theme
description: >-
Choose the game theme which mainly affects the map background. Notice that everything except the light theme may lead to graphical issues.
refreshRate:
title: Simulation Target
description: >-
If you have a 144hz monitor, change the refresh rate here so the game will properly simulate at higher refresh rates.