mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Major performance improvements
This commit is contained in:
parent
260ba892c8
commit
2c48cb72aa
@ -7,4 +7,5 @@
|
||||
display: flex;
|
||||
line-height: 15px;
|
||||
flex-direction: column;
|
||||
color: #fff;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ body.uiHidden {
|
||||
|
||||
body.modalDialogActive,
|
||||
body.ingameDialogOpen {
|
||||
> *:not(.ingameDialog):not(.modalDialogParent) {
|
||||
> *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog) {
|
||||
filter: blur(5px) !important;
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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];
|
||||
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user