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

Add multiple performance settings

This commit is contained in:
tobspr
2020-08-14 09:38:48 +02:00
parent 9e76606674
commit 9085f32ec3
16 changed files with 241 additions and 255 deletions

View File

@@ -1,8 +1,6 @@
/* typehints:start */
import { InGameState } from "../states/ingame";
import { Application } from "../application";
/* typehints:end */
import { BufferMaintainer } from "../core/buffer_maintainer";
import { disableImageSmoothing, enableImageSmoothing, registerCanvas } from "../core/buffer_utils";
import { globalConfig } from "../core/config";
@@ -10,12 +8,16 @@ import { getDeviceDPI, resizeHighDPICanvas } from "../core/dpi_manager";
import { DrawParameters } from "../core/draw_parameters";
import { gMetaBuildingRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging";
import { Rectangle } from "../core/rectangle";
import { randomInt } from "../core/utils";
import { Vector } from "../core/vector";
import { Savegame } from "../savegame/savegame";
import { SavegameSerializer } from "../savegame/savegame_serializer";
import { InGameState } from "../states/ingame";
import { AutomaticSave } from "./automatic_save";
import { MetaHubBuilding } from "./buildings/hub";
import { Camera } from "./camera";
import { DynamicTickrate } from "./dynamic_tickrate";
import { EntityManager } from "./entity_manager";
import { GameSystemManager } from "./game_system_manager";
import { HubGoals } from "./hub_goals";
@@ -23,15 +25,12 @@ import { GameHUD } from "./hud/hud";
import { KeyActionMapper } from "./key_action_mapper";
import { GameLogic } from "./logic";
import { MapView } from "./map_view";
import { GameRoot, enumLayer } from "./root";
import { defaultBuildingVariant } from "./meta_building";
import { ProductionAnalytics } from "./production_analytics";
import { enumLayer, GameRoot } from "./root";
import { ShapeDefinitionManager } from "./shape_definition_manager";
import { SoundProxy } from "./sound_proxy";
import { GameTime } from "./time/game_time";
import { ProductionAnalytics } from "./production_analytics";
import { randomInt } from "../core/utils";
import { defaultBuildingVariant } from "./meta_building";
import { DynamicTickrate } from "./dynamic_tickrate";
import { Rectangle } from "../core/rectangle";
const logger = createLogger("ingame/core");
@@ -233,10 +232,6 @@ export class GameCore {
tick(deltaMs) {
const root = this.root;
if (root.hud.parts.processingOverlay.hasTasks() || root.hud.parts.processingOverlay.isRunning()) {
return true;
}
// Extract current real time
root.time.updateRealtimeNow();
@@ -326,14 +321,6 @@ export class GameCore {
const root = this.root;
const systems = root.systemMgr.systems;
const taskRunner = root.hud.parts.processingOverlay;
if (taskRunner.hasTasks()) {
if (!taskRunner.isRunning()) {
taskRunner.process();
}
return;
}
this.root.dynamicTickrate.onFrameRendered();
if (!this.shouldRender()) {
@@ -357,15 +344,16 @@ export class GameCore {
// Compute optimal zoom level and atlas scale
const zoomLevel = root.camera.zoomLevel;
const lowQuality = root.app.settings.getAllSettings().lowQualityTextures;
const effectiveZoomLevel =
(zoomLevel / globalConfig.assetsDpi) * getDeviceDPI() * globalConfig.assetsSharpness;
let desiredAtlasScale = "0.1";
if (effectiveZoomLevel > 0.75) {
if (effectiveZoomLevel > 0.75 && !lowQuality) {
desiredAtlasScale = "1";
} else if (effectiveZoomLevel > 0.5) {
} else if (effectiveZoomLevel > 0.5 && !lowQuality) {
desiredAtlasScale = "0.75";
} else if (effectiveZoomLevel > 0.25) {
} else if (effectiveZoomLevel > 0.25 && !lowQuality) {
desiredAtlasScale = "0.5";
} else if (effectiveZoomLevel > 0.1) {
desiredAtlasScale = "0.25";
@@ -380,7 +368,7 @@ export class GameCore {
root: root,
});
if (G_IS_DEV && (globalConfig.debug.testCulling || globalConfig.debug.hideFog)) {
if (G_IS_DEV && globalConfig.debug.testCulling) {
context.clearRect(0, 0, root.gameWidth, root.gameHeight);
}

View File

@@ -8,7 +8,6 @@ import { TrailerMaker } from "./trailer_maker";
import { Signal } from "../../core/signal";
import { DrawParameters } from "../../core/draw_parameters";
import { HUDProcessingOverlay } from "./parts/processing_overlay";
import { HUDBuildingsToolbar } from "./parts/buildings_toolbar";
import { HUDBuildingPlacer } from "./parts/building_placer";
import { HUDBlueprintPlacer } from "./parts/blueprint_placer";
@@ -57,7 +56,6 @@ export class GameHUD {
*/
initialize() {
this.parts = {
processingOverlay: new HUDProcessingOverlay(this.root),
buildingsToolbar: new HUDBuildingsToolbar(this.root),
wiresToolbar: new HUDWiresToolbar(this.root),
blueprintPlacer: new HUDBlueprintPlacer(this.root),

View File

@@ -1,112 +0,0 @@
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { BaseHUDPart } from "../base_hud_part";
import { makeDiv } from "../../../core/utils";
import { Signal } from "../../../core/signal";
import { InputReceiver } from "../../../core/input_receiver";
import { createLogger } from "../../../core/logging";
const logger = createLogger("hud/processing_overlay");
export class HUDProcessingOverlay extends BaseHUDPart {
constructor(root) {
super(root);
this.tasks = [];
this.computeTimeout = null;
this.root.signals.performAsync.add(this.queueTask, this);
this.allTasksFinished = new Signal();
this.inputReceiver = new InputReceiver("processing-overlay");
this.root.signals.aboutToDestruct.add(() =>
this.root.app.inputMgr.destroyReceiver(this.inputReceiver)
);
}
createElements(parent) {
this.element = makeDiv(
parent,
"rg_HUD_ProcessingOverlay",
["hudElement"],
`
<span class="prefab_LoadingTextWithAnim">
Computing
</span>
`
);
}
initialize() {
this.domWatcher = new DynamicDomAttach(this.root, this.element, {
timeToKeepSeconds: 0,
});
}
queueTask(task, name) {
if (!this.root.gameInitialized) {
// Tasks before the game started can be done directlry
task();
return;
}
task.__name = name;
this.tasks.push(task);
}
hasTasks() {
return this.tasks.length > 0;
}
isRunning() {
return this.computeTimeout !== null;
}
processSync() {
const now = performance.now();
while (this.tasks.length > 0) {
const workload = this.tasks[0];
workload.call();
this.tasks.shift();
}
const duration = performance.now() - now;
if (duration > 100) {
logger.log("Tasks done slow (SYNC!) within", (performance.now() - now).toFixed(2), "ms");
}
}
process() {
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReceiver);
this.domWatcher.update(true);
if (this.tasks.length === 0) {
logger.warn("No tasks but still called process");
return;
}
if (this.computeTimeout) {
assert(false, "Double compute queued");
clearTimeout(this.computeTimeout);
}
this.computeTimeout = setTimeout(() => {
const now = performance.now();
while (this.tasks.length > 0) {
const workload = this.tasks[0];
workload.call();
this.tasks.shift();
}
const duration = performance.now() - now;
if (duration > 100) {
logger.log("Tasks done slow within", (performance.now() - now).toFixed(2), "ms");
}
this.domWatcher.update(false);
this.root.app.inputMgr.makeSureDetached(this.inputReceiver);
clearTimeout(this.computeTimeout);
this.computeTimeout = null;
this.allTasksFinished.dispatch();
});
}
}

View File

@@ -63,7 +63,7 @@ export class HUDScreenshotExporter extends BaseHUDPart {
}
logger.log("ChunkSizePixels:", chunkSizePixels);
const chunkScale = chunkSizePixels / (globalConfig.mapChunkSize * globalConfig.tileSize);
const chunkScale = chunkSizePixels / globalConfig.mapChunkWorldSize;
logger.log("Scale:", chunkScale);
logger.log("Allocating buffer, if the factory grew too big it will crash here");
@@ -79,10 +79,10 @@ export class HUDScreenshotExporter extends BaseHUDPart {
logger.log("Got buffer, rendering now ...");
const visibleRect = new Rectangle(
minChunk.x * globalConfig.mapChunkSize * globalConfig.tileSize,
minChunk.y * globalConfig.mapChunkSize * globalConfig.tileSize,
dimensions.x * globalConfig.mapChunkSize * globalConfig.tileSize,
dimensions.y * globalConfig.mapChunkSize * globalConfig.tileSize
minChunk.x * globalConfig.mapChunkWorldSize,
minChunk.y * globalConfig.mapChunkWorldSize,
dimensions.x * globalConfig.mapChunkWorldSize,
dimensions.y * globalConfig.mapChunkWorldSize
);
const parameters = new DrawParameters({
context,

View File

@@ -1,7 +1,12 @@
import { GameRoot, enumLayer } from "./root";
import { globalConfig } from "../core/config";
import { createLogger } from "../core/logging";
import { clamp, fastArrayDeleteValueIfContained, make2DUndefinedArray } from "../core/utils";
import {
clamp,
fastArrayDeleteValueIfContained,
make2DUndefinedArray,
fastArrayDeleteValue,
} from "../core/utils";
import { Vector } from "../core/vector";
import { BaseItem } from "./base_item";
import { enumColors } from "./colors";
@@ -39,6 +44,15 @@ export class MapChunk {
/** @type {Array<Entity>} */
this.containedEntities = [];
/**
* Which entities this chunk contains, sorted by layer
* @type {Object<enumLayer, Array<Entity>>}
*/
this.containedEntitiesByLayer = {
[enumLayer.regular]: [],
[enumLayer.wires]: [],
};
/**
* Store which patches we have so we can render them in the overview
* @type {Array<{pos: Vector, item: BaseItem, size: number }>}
@@ -403,8 +417,9 @@ export class MapChunk {
assert(contents === null || !oldContents, "Tile already used: " + tileX + " / " + tileY);
if (oldContents) {
// Remove from list
// Remove from list (the old contents must be reigstered)
fastArrayDeleteValueIfContained(this.containedEntities, oldContents);
fastArrayDeleteValueIfContained(this.containedEntitiesByLayer[layer], oldContents);
}
if (layer === enumLayer.regular) {
@@ -417,6 +432,10 @@ export class MapChunk {
if (this.containedEntities.indexOf(contents) < 0) {
this.containedEntities.push(contents);
}
if (this.containedEntitiesByLayer[layer].indexOf(contents) < 0) {
this.containedEntitiesByLayer[layer].push(contents);
}
}
}
}

View File

@@ -69,13 +69,14 @@ export class MapChunkView extends MapChunk {
redrawMethod: this.generateOverlayBuffer.bind(this),
});
const dims = globalConfig.mapChunkSize * globalConfig.tileSize;
const dims = globalConfig.mapChunkWorldSize;
// Draw chunk "pixel" art
parameters.context.imageSmoothingEnabled = false;
parameters.context.drawImage(sprite, this.x * dims, this.y * dims, dims, dims);
parameters.context.imageSmoothingEnabled = true;
// Draw patch items
if (this.root.currentLayer === enumLayer.regular) {
for (let i = 0; i < this.patches.length; ++i) {
const patch = this.patches[i];

View File

@@ -195,17 +195,19 @@ export class MapView extends BaseMap {
);
}
const dpi = this.backgroundCacheDPI;
parameters.context.scale(1 / dpi, 1 / dpi);
if (!this.root.app.settings.getAllSettings().disableTileGrid) {
const dpi = this.backgroundCacheDPI;
parameters.context.scale(1 / dpi, 1 / dpi);
parameters.context.fillStyle = this.cachedBackgroundPattern;
parameters.context.fillRect(
parameters.visibleRect.x * dpi,
parameters.visibleRect.y * dpi,
parameters.visibleRect.w * dpi,
parameters.visibleRect.h * dpi
);
parameters.context.scale(dpi, dpi);
parameters.context.fillStyle = this.cachedBackgroundPattern;
parameters.context.fillRect(
parameters.visibleRect.x * dpi,
parameters.visibleRect.y * dpi,
parameters.visibleRect.w * dpi,
parameters.visibleRect.h * dpi
);
parameters.context.scale(dpi, dpi);
}
this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer);
@@ -233,16 +235,16 @@ export class MapView extends BaseMap {
for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
parameters.context.fillStyle = "#ffaaaa";
parameters.context.fillRect(
chunkX * globalConfig.mapChunkSize * globalConfig.tileSize,
chunkY * globalConfig.mapChunkSize * globalConfig.tileSize,
globalConfig.mapChunkSize * globalConfig.tileSize,
chunkX * globalConfig.mapChunkWorldSize,
chunkY * globalConfig.mapChunkWorldSize,
globalConfig.mapChunkWorldSize,
3
);
parameters.context.fillRect(
chunkX * globalConfig.mapChunkSize * globalConfig.tileSize,
chunkY * globalConfig.mapChunkSize * globalConfig.tileSize,
chunkX * globalConfig.mapChunkWorldSize,
chunkY * globalConfig.mapChunkWorldSize,
3,
globalConfig.mapChunkSize * globalConfig.tileSize
globalConfig.mapChunkWorldSize
);
}
}

View File

@@ -162,9 +162,6 @@ export class GameRoot {
// Called right after game is initialized
postLoadHook: /** @type {TypedSignal<[]>} */ (new Signal()),
// Can be used to trigger an async task
performAsync: /** @type {TypedSignal<[function]>} */ (new Signal()),
shapeDelivered: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),

View File

@@ -4,6 +4,7 @@ import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item
import { MapChunkView } from "../map_chunk_view";
import { globalConfig } from "../../core/config";
import { Loader } from "../../core/loader";
import { enumLayer } from "../root";
export class LeverSystem extends GameSystemWithFilter {
constructor(root) {
@@ -31,23 +32,19 @@ export class LeverSystem extends GameSystemWithFilter {
* @param {MapChunkView} chunk
*/
drawChunk(parameters, chunk) {
const contents = chunk.contents;
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
const entity = contents[x][y];
if (entity && entity.components.Lever) {
const sprite = entity.components.Lever.toggled ? this.spriteOn : this.spriteOff;
const origin = entity.components.StaticMapEntity.origin;
sprite.drawCached(
parameters,
origin.x * globalConfig.tileSize,
origin.y * globalConfig.tileSize,
globalConfig.tileSize,
globalConfig.tileSize
);
}
const contents = chunk.containedEntitiesByLayer[enumLayer.regular];
for (let i = 0; i < contents.length; ++i) {
const entity = contents[i];
if (entity && entity.components.Lever) {
const sprite = entity.components.Lever.toggled ? this.spriteOn : this.spriteOff;
const origin = entity.components.StaticMapEntity.origin;
sprite.drawCached(
parameters,
origin.x * globalConfig.tileSize,
origin.y * globalConfig.tileSize,
globalConfig.tileSize,
globalConfig.tileSize
);
}
}
}

View File

@@ -1,7 +1,8 @@
import { GameSystem } from "../game_system";
import { DrawParameters } from "../../core/draw_parameters";
import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters";
import { GameSystem } from "../game_system";
import { MapChunkView } from "../map_chunk_view";
import { THEME } from "../theme";
export class MapResourcesSystem extends GameSystem {
/**
@@ -10,39 +11,106 @@ export class MapResourcesSystem extends GameSystem {
* @param {MapChunkView} chunk
*/
drawChunk(parameters, chunk) {
const basicChunkBackground = this.root.buffers.getForKey({
key: "chunkres",
subKey: chunk.renderKey,
w: globalConfig.mapChunkSize,
h: globalConfig.mapChunkSize,
dpi: 1,
redrawMethod: this.generateChunkBackground.bind(this, chunk),
});
parameters.context.imageSmoothingEnabled = false;
parameters.context.drawImage(
basicChunkBackground,
chunk.tileX * globalConfig.tileSize,
chunk.tileY * globalConfig.tileSize,
globalConfig.mapChunkWorldSize,
globalConfig.mapChunkWorldSize
);
parameters.context.imageSmoothingEnabled = true;
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 (this.root.app.settings.getAllSettings().lowQualityMapResources) {
// LOW QUALITY: Draw patch items only
for (let i = 0; i < chunk.patches.length; ++i) {
const patch = chunk.patches[i];
if (
!parameters.visibleRect.containsRect4Params(
worldX,
worldY,
globalConfig.tileSize,
globalConfig.tileSize
)
) {
// Clipped
continue;
patch.item.draw(
chunk.x * globalConfig.mapChunkWorldSize + patch.pos.x * globalConfig.tileSize,
chunk.y * globalConfig.mapChunkWorldSize + patch.pos.y * globalConfig.tileSize,
parameters,
Math.min(80, 40 / parameters.zoomLevel)
);
}
} else {
// HIGH QUALITY: Draw all items
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(worldX, worldY, globalConfig.tileSize, globalConfig.tileSize);
lowerItem.draw(
worldX + globalConfig.halfTileSize,
worldY + globalConfig.halfTileSize,
parameters
);
}
parameters.context.fillStyle = lowerItem.getBackgroundColorAsResource();
parameters.context.fillRect(worldX, worldY, globalConfig.tileSize, globalConfig.tileSize);
lowerItem.draw(
worldX + globalConfig.halfTileSize,
worldY + globalConfig.halfTileSize,
parameters
);
}
}
}
parameters.context.globalAlpha = 1;
}
/**
*
* @param {MapChunkView} chunk
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} context
* @param {number} w
* @param {number} h
* @param {number} dpi
*/
generateChunkBackground(chunk, canvas, context, w, h, dpi) {
if (this.root.app.settings.getAllSettings().disableTileGrid) {
// The map doesn't draw a background, so we have to
context.fillStyle = THEME.map.background;
context.fillRect(0, 0, w, h);
} else {
context.clearRect(0, 0, w, h);
}
context.globalAlpha = 0.5;
const layer = chunk.lowerLayer;
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
const row = layer[x];
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
const item = row[y];
if (item) {
context.fillStyle = item.getBackgroundColorAsResource();
context.fillRect(x, y, 1, 1);
}
}
}
}
}

View File

@@ -153,11 +153,6 @@ export class GameTime extends BasicSerializableObject {
);
break;
}
// If we queued async tasks, perform them next frame and do not update anymore
if (this.root.hud.parts.processingOverlay.hasTasks()) {
break;
}
}
}