Refactor statistics and fix crash

pull/33/head
tobspr 4 years ago
parent 9898916807
commit bf79f17776

@ -1,224 +1,11 @@
import { BaseHUDPart } from "../base_hud_part";
import {
makeDiv,
makeButton,
formatBigNumber,
clamp,
removeAllChildren,
waitNextFrame,
} from "../../../core/utils";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { Math_min } from "../../../core/builtins";
import { InputReceiver } from "../../../core/input_receiver";
import { makeButton, makeDiv, removeAllChildren } from "../../../core/utils";
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 { globalConfig } from "../../../core/config";
import { Math_floor, Math_min } from "../../../core/builtins";
/** @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;
}
}
}
import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { enumDisplayMode, HUDShapeStatisticsHandle } from "./statistics_handle";
export class HUDStatistics extends BaseHUDPart {
createElements(parent) {
@ -285,7 +72,7 @@ export class HUDStatistics extends BaseHUDPart {
this.keyActionMapper.getBinding("back").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.setDataSource(enumAnalyticsDataSource.produced);
@ -342,6 +129,9 @@ export class HUDStatistics extends BaseHUDPart {
}
}
/**
* Performs a partial rerender, only updating graphs and counts
*/
rerenderPartial() {
for (const key in this.activeHandles) {
const handle = this.activeHandles[key];
@ -349,6 +139,9 @@ export class HUDStatistics extends BaseHUDPart {
}
}
/**
* Performs a full rerender, regenerating everything
*/
rerenderFull() {
removeAllChildren(this.contentDiv);
@ -369,7 +162,7 @@ export class HUDStatistics extends BaseHUDPart {
let handle = this.activeHandles[shapeKey];
if (!handle) {
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(shapeKey);
handle = this.activeHandles[shapeKey] = new ShapeStatisticsHandle(
handle = this.activeHandles[shapeKey] = new HUDShapeStatisticsHandle(
this.root,
definition,
this.intersectionObserver

@ -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…
Cancel
Save