mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Refactor statistics and fix crash
This commit is contained in:
parent
9898916807
commit
bf79f17776
@ -1,224 +1,11 @@
|
|||||||
import { BaseHUDPart } from "../base_hud_part";
|
import { Math_min } from "../../../core/builtins";
|
||||||
import {
|
|
||||||
makeDiv,
|
|
||||||
makeButton,
|
|
||||||
formatBigNumber,
|
|
||||||
clamp,
|
|
||||||
removeAllChildren,
|
|
||||||
waitNextFrame,
|
|
||||||
} from "../../../core/utils";
|
|
||||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
|
||||||
import { InputReceiver } from "../../../core/input_receiver";
|
import { InputReceiver } from "../../../core/input_receiver";
|
||||||
|
import { makeButton, makeDiv, removeAllChildren } from "../../../core/utils";
|
||||||
import { KeyActionMapper } from "../../key_action_mapper";
|
import { KeyActionMapper } from "../../key_action_mapper";
|
||||||
import { ShapeDefinition } from "../../shape_definition";
|
|
||||||
import { GameRoot } from "../../root";
|
|
||||||
import { freeCanvas, makeOffscreenBuffer } from "../../../core/buffer_utils";
|
|
||||||
import { enumAnalyticsDataSource } from "../../production_analytics";
|
import { enumAnalyticsDataSource } from "../../production_analytics";
|
||||||
import { globalConfig } from "../../../core/config";
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
import { Math_floor, Math_min } from "../../../core/builtins";
|
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||||
|
import { enumDisplayMode, HUDShapeStatisticsHandle } from "./statistics_handle";
|
||||||
/** @enum {string} */
|
|
||||||
const enumDisplayMode = {
|
|
||||||
icons: "icons",
|
|
||||||
detailed: "detailed",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple wrapper for a shape definition
|
|
||||||
*/
|
|
||||||
class ShapeStatisticsHandle {
|
|
||||||
/**
|
|
||||||
* @param {GameRoot} root
|
|
||||||
* @param {ShapeDefinition} definition
|
|
||||||
* @param {IntersectionObserver} intersectionObserver
|
|
||||||
*/
|
|
||||||
constructor(root, definition, intersectionObserver) {
|
|
||||||
this.definition = definition;
|
|
||||||
this.root = root;
|
|
||||||
this.intersectionObserver = intersectionObserver;
|
|
||||||
|
|
||||||
this.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
initElement() {
|
|
||||||
this.element = document.createElement("div");
|
|
||||||
this.element.setAttribute("data-shape-key", this.definition.getHash());
|
|
||||||
|
|
||||||
this.counter = document.createElement("span");
|
|
||||||
this.counter.classList.add("counter");
|
|
||||||
this.element.appendChild(this.counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the shape handle is visible currently
|
|
||||||
* @param {boolean} visibility
|
|
||||||
*/
|
|
||||||
setVisible(visibility) {
|
|
||||||
if (visibility === this.visible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.visible = visibility;
|
|
||||||
if (visibility) {
|
|
||||||
if (!this.shapeCanvas) {
|
|
||||||
// Create elements
|
|
||||||
this.shapeCanvas = this.definition.generateAsCanvas(100);
|
|
||||||
this.shapeCanvas.classList.add("icon");
|
|
||||||
this.element.appendChild(this.shapeCanvas);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Drop elements
|
|
||||||
if (this.shapeCanvas) {
|
|
||||||
this.shapeCanvas.remove();
|
|
||||||
delete this.shapeCanvas;
|
|
||||||
}
|
|
||||||
if (this.graphCanvas) {
|
|
||||||
this.graphCanvas.remove();
|
|
||||||
delete this.graphCanvas;
|
|
||||||
delete this.graphContext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {enumDisplayMode} displayMode
|
|
||||||
* @param {enumAnalyticsDataSource} dataSource
|
|
||||||
* @param {boolean=} forced
|
|
||||||
*/
|
|
||||||
update(displayMode, dataSource, forced = false) {
|
|
||||||
if (!this.element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.visible && !forced) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (dataSource) {
|
|
||||||
case enumAnalyticsDataSource.stored: {
|
|
||||||
this.counter.innerText = formatBigNumber(
|
|
||||||
this.root.hubGoals.storedShapes[this.definition.getHash()] || 0
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case enumAnalyticsDataSource.delivered:
|
|
||||||
case enumAnalyticsDataSource.produced: {
|
|
||||||
let rate =
|
|
||||||
(this.root.productionAnalytics.getCurrentShapeRate(dataSource, this.definition) /
|
|
||||||
globalConfig.analyticsSliceDurationSeconds) *
|
|
||||||
60;
|
|
||||||
this.counter.innerText = formatBigNumber(rate) + " / m";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayMode === enumDisplayMode.detailed) {
|
|
||||||
const graphDpi = globalConfig.statisticsGraphDpi;
|
|
||||||
|
|
||||||
const w = 300;
|
|
||||||
const h = 40;
|
|
||||||
|
|
||||||
if (!this.graphCanvas) {
|
|
||||||
const [canvas, context] = makeOffscreenBuffer(w * graphDpi, h * graphDpi, {
|
|
||||||
smooth: true,
|
|
||||||
reusable: false,
|
|
||||||
label: "statgraph-" + this.definition.getHash(),
|
|
||||||
});
|
|
||||||
context.scale(graphDpi, graphDpi);
|
|
||||||
canvas.classList.add("graph");
|
|
||||||
this.graphCanvas = canvas;
|
|
||||||
this.graphContext = context;
|
|
||||||
this.element.appendChild(this.graphCanvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.graphContext.clearRect(0, 0, w, h);
|
|
||||||
|
|
||||||
this.graphContext.fillStyle = "#bee0db";
|
|
||||||
this.graphContext.strokeStyle = "#66ccbc";
|
|
||||||
this.graphContext.lineWidth = 1.5;
|
|
||||||
|
|
||||||
const sliceWidth = w / globalConfig.statisticsGraphSlices;
|
|
||||||
|
|
||||||
let values = [];
|
|
||||||
let maxValue = 1;
|
|
||||||
|
|
||||||
for (let i = 0; i < globalConfig.statisticsGraphSlices - 1; ++i) {
|
|
||||||
const value = this.root.productionAnalytics.getPastShapeRate(
|
|
||||||
dataSource,
|
|
||||||
this.definition,
|
|
||||||
globalConfig.statisticsGraphSlices - i - 1
|
|
||||||
);
|
|
||||||
if (value > maxValue) {
|
|
||||||
maxValue = value;
|
|
||||||
}
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.graphContext.beginPath();
|
|
||||||
this.graphContext.moveTo(0.75, h + 5);
|
|
||||||
for (let i = 0; i < values.length; ++i) {
|
|
||||||
const yValue = clamp((1 - values[i] / maxValue) * h, 0.75, h - 0.75);
|
|
||||||
const x = i * sliceWidth;
|
|
||||||
if (i === 0) {
|
|
||||||
this.graphContext.lineTo(0.75, yValue);
|
|
||||||
}
|
|
||||||
this.graphContext.lineTo(x, yValue);
|
|
||||||
if (i === values.length - 1) {
|
|
||||||
this.graphContext.lineTo(w + 100, yValue);
|
|
||||||
this.graphContext.lineTo(w + 100, h + 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.graphContext.closePath();
|
|
||||||
this.graphContext.stroke();
|
|
||||||
this.graphContext.fill();
|
|
||||||
} else {
|
|
||||||
if (this.graphCanvas) {
|
|
||||||
this.graphCanvas.remove();
|
|
||||||
delete this.graphCanvas;
|
|
||||||
delete this.graphContext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches the handle
|
|
||||||
* @param {HTMLElement} parent
|
|
||||||
*/
|
|
||||||
attach(parent) {
|
|
||||||
if (!this.element) {
|
|
||||||
this.initElement();
|
|
||||||
}
|
|
||||||
if (this.element.parentElement !== parent) {
|
|
||||||
parent.appendChild(this.element);
|
|
||||||
this.intersectionObserver.observe(this.element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detaches the handle
|
|
||||||
*/
|
|
||||||
detach() {
|
|
||||||
if (this.element && this.element.parentElement) {
|
|
||||||
this.element.parentElement.removeChild(this.element);
|
|
||||||
this.intersectionObserver.unobserve(this.element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the handle
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
if (this.element) {
|
|
||||||
this.intersectionObserver.unobserve(this.element);
|
|
||||||
this.shapeCanvas.remove();
|
|
||||||
|
|
||||||
this.element.remove();
|
|
||||||
delete this.element;
|
|
||||||
delete this.counter;
|
|
||||||
delete this.shapeCanvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HUDStatistics extends BaseHUDPart {
|
export class HUDStatistics extends BaseHUDPart {
|
||||||
createElements(parent) {
|
createElements(parent) {
|
||||||
@ -285,7 +72,7 @@ export class HUDStatistics extends BaseHUDPart {
|
|||||||
this.keyActionMapper.getBinding("back").add(this.close, this);
|
this.keyActionMapper.getBinding("back").add(this.close, this);
|
||||||
this.keyActionMapper.getBinding("menu_open_stats").add(this.close, this);
|
this.keyActionMapper.getBinding("menu_open_stats").add(this.close, this);
|
||||||
|
|
||||||
/** @type {Object.<string, ShapeStatisticsHandle>} */
|
/** @type {Object.<string, HUDShapeStatisticsHandle>} */
|
||||||
this.activeHandles = {};
|
this.activeHandles = {};
|
||||||
|
|
||||||
this.setDataSource(enumAnalyticsDataSource.produced);
|
this.setDataSource(enumAnalyticsDataSource.produced);
|
||||||
@ -342,6 +129,9 @@ export class HUDStatistics extends BaseHUDPart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a partial rerender, only updating graphs and counts
|
||||||
|
*/
|
||||||
rerenderPartial() {
|
rerenderPartial() {
|
||||||
for (const key in this.activeHandles) {
|
for (const key in this.activeHandles) {
|
||||||
const handle = this.activeHandles[key];
|
const handle = this.activeHandles[key];
|
||||||
@ -349,6 +139,9 @@ export class HUDStatistics extends BaseHUDPart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a full rerender, regenerating everything
|
||||||
|
*/
|
||||||
rerenderFull() {
|
rerenderFull() {
|
||||||
removeAllChildren(this.contentDiv);
|
removeAllChildren(this.contentDiv);
|
||||||
|
|
||||||
@ -369,7 +162,7 @@ export class HUDStatistics extends BaseHUDPart {
|
|||||||
let handle = this.activeHandles[shapeKey];
|
let handle = this.activeHandles[shapeKey];
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(shapeKey);
|
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(shapeKey);
|
||||||
handle = this.activeHandles[shapeKey] = new ShapeStatisticsHandle(
|
handle = this.activeHandles[shapeKey] = new HUDShapeStatisticsHandle(
|
||||||
this.root,
|
this.root,
|
||||||
definition,
|
definition,
|
||||||
this.intersectionObserver
|
this.intersectionObserver
|
||||||
|
217
src/js/game/hud/parts/statistics_handle.js
Normal file
217
src/js/game/hud/parts/statistics_handle.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import { GameRoot } from "../../root";
|
||||||
|
import { ShapeDefinition } from "../../shape_definition";
|
||||||
|
import { enumAnalyticsDataSource } from "../../production_analytics";
|
||||||
|
import { formatBigNumber, clamp } from "../../../core/utils";
|
||||||
|
import { globalConfig } from "../../../core/config";
|
||||||
|
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||||
|
|
||||||
|
/** @enum {string} */
|
||||||
|
export const enumDisplayMode = {
|
||||||
|
icons: "icons",
|
||||||
|
detailed: "detailed",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple wrapper for a shape definition within the shape statistics
|
||||||
|
*/
|
||||||
|
export class HUDShapeStatisticsHandle {
|
||||||
|
/**
|
||||||
|
* @param {GameRoot} root
|
||||||
|
* @param {ShapeDefinition} definition
|
||||||
|
* @param {IntersectionObserver} intersectionObserver
|
||||||
|
*/
|
||||||
|
constructor(root, definition, intersectionObserver) {
|
||||||
|
this.definition = definition;
|
||||||
|
this.root = root;
|
||||||
|
this.intersectionObserver = intersectionObserver;
|
||||||
|
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
initElement() {
|
||||||
|
this.element = document.createElement("div");
|
||||||
|
this.element.setAttribute("data-shape-key", this.definition.getHash());
|
||||||
|
|
||||||
|
this.counter = document.createElement("span");
|
||||||
|
this.counter.classList.add("counter");
|
||||||
|
this.element.appendChild(this.counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the shape handle is visible currently
|
||||||
|
* @param {boolean} visibility
|
||||||
|
*/
|
||||||
|
setVisible(visibility) {
|
||||||
|
if (visibility === this.visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.visible = visibility;
|
||||||
|
if (visibility) {
|
||||||
|
if (!this.shapeCanvas) {
|
||||||
|
// Create elements
|
||||||
|
this.shapeCanvas = this.definition.generateAsCanvas(100);
|
||||||
|
this.shapeCanvas.classList.add("icon");
|
||||||
|
this.element.appendChild(this.shapeCanvas);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Drop elements
|
||||||
|
this.cleanupChildElements();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {enumDisplayMode} displayMode
|
||||||
|
* @param {enumAnalyticsDataSource} dataSource
|
||||||
|
* @param {boolean=} forced
|
||||||
|
*/
|
||||||
|
update(displayMode, dataSource, forced = false) {
|
||||||
|
if (!this.element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.visible && !forced) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (dataSource) {
|
||||||
|
case enumAnalyticsDataSource.stored: {
|
||||||
|
this.counter.innerText = formatBigNumber(
|
||||||
|
this.root.hubGoals.storedShapes[this.definition.getHash()] || 0
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case enumAnalyticsDataSource.delivered:
|
||||||
|
case enumAnalyticsDataSource.produced: {
|
||||||
|
let rate =
|
||||||
|
(this.root.productionAnalytics.getCurrentShapeRate(dataSource, this.definition) /
|
||||||
|
globalConfig.analyticsSliceDurationSeconds) *
|
||||||
|
60;
|
||||||
|
this.counter.innerText = formatBigNumber(rate) + " / m";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayMode === enumDisplayMode.detailed) {
|
||||||
|
const graphDpi = globalConfig.statisticsGraphDpi;
|
||||||
|
|
||||||
|
const w = 300;
|
||||||
|
const h = 40;
|
||||||
|
|
||||||
|
if (!this.graphCanvas) {
|
||||||
|
const [canvas, context] = makeOffscreenBuffer(w * graphDpi, h * graphDpi, {
|
||||||
|
smooth: true,
|
||||||
|
reusable: false,
|
||||||
|
label: "statgraph-" + this.definition.getHash(),
|
||||||
|
});
|
||||||
|
context.scale(graphDpi, graphDpi);
|
||||||
|
canvas.classList.add("graph");
|
||||||
|
this.graphCanvas = canvas;
|
||||||
|
this.graphContext = context;
|
||||||
|
this.element.appendChild(this.graphCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.graphContext.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
|
this.graphContext.fillStyle = "#bee0db";
|
||||||
|
this.graphContext.strokeStyle = "#66ccbc";
|
||||||
|
this.graphContext.lineWidth = 1.5;
|
||||||
|
|
||||||
|
const sliceWidth = w / globalConfig.statisticsGraphSlices;
|
||||||
|
|
||||||
|
let values = [];
|
||||||
|
let maxValue = 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < globalConfig.statisticsGraphSlices - 1; ++i) {
|
||||||
|
const value = this.root.productionAnalytics.getPastShapeRate(
|
||||||
|
dataSource,
|
||||||
|
this.definition,
|
||||||
|
globalConfig.statisticsGraphSlices - i - 1
|
||||||
|
);
|
||||||
|
if (value > maxValue) {
|
||||||
|
maxValue = value;
|
||||||
|
}
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.graphContext.beginPath();
|
||||||
|
this.graphContext.moveTo(0.75, h + 5);
|
||||||
|
for (let i = 0; i < values.length; ++i) {
|
||||||
|
const yValue = clamp((1 - values[i] / maxValue) * h, 0.75, h - 0.75);
|
||||||
|
const x = i * sliceWidth;
|
||||||
|
if (i === 0) {
|
||||||
|
this.graphContext.lineTo(0.75, yValue);
|
||||||
|
}
|
||||||
|
this.graphContext.lineTo(x, yValue);
|
||||||
|
if (i === values.length - 1) {
|
||||||
|
this.graphContext.lineTo(w + 100, yValue);
|
||||||
|
this.graphContext.lineTo(w + 100, h + 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.graphContext.closePath();
|
||||||
|
this.graphContext.stroke();
|
||||||
|
this.graphContext.fill();
|
||||||
|
} else {
|
||||||
|
if (this.graphCanvas) {
|
||||||
|
this.graphCanvas.remove();
|
||||||
|
delete this.graphCanvas;
|
||||||
|
delete this.graphContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches the handle
|
||||||
|
* @param {HTMLElement} parent
|
||||||
|
*/
|
||||||
|
attach(parent) {
|
||||||
|
if (!this.element) {
|
||||||
|
this.initElement();
|
||||||
|
}
|
||||||
|
if (this.element.parentElement !== parent) {
|
||||||
|
parent.appendChild(this.element);
|
||||||
|
this.intersectionObserver.observe(this.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detaches the handle
|
||||||
|
*/
|
||||||
|
detach() {
|
||||||
|
if (this.element && this.element.parentElement) {
|
||||||
|
this.element.parentElement.removeChild(this.element);
|
||||||
|
this.intersectionObserver.unobserve(this.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up all child elements
|
||||||
|
*/
|
||||||
|
cleanupChildElements() {
|
||||||
|
if (this.shapeCanvas) {
|
||||||
|
this.shapeCanvas.remove();
|
||||||
|
delete this.shapeCanvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.graphCanvas) {
|
||||||
|
this.graphCanvas.remove();
|
||||||
|
delete this.graphCanvas;
|
||||||
|
delete this.graphContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the handle
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
this.cleanupChildElements();
|
||||||
|
if (this.element) {
|
||||||
|
this.intersectionObserver.unobserve(this.element);
|
||||||
|
this.element.remove();
|
||||||
|
delete this.element;
|
||||||
|
|
||||||
|
// Remove handle
|
||||||
|
delete this.counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user