1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

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) {