Optimize performance by using singletons for items

This commit is contained in:
tobspr 2020-08-14 13:09:10 +02:00
parent 3c34227c24
commit 8c39d31c5b
18 changed files with 145 additions and 88 deletions

View File

@ -1,14 +1,14 @@
import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters";
import { gItemRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging";
import { Rectangle } from "../core/rectangle";
import { epsilonCompare, round4Digits } from "../core/utils";
import { enumDirection, enumDirectionToVector, Vector, enumInvertedDirections } from "../core/vector";
import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { BaseItem } from "./base_item";
import { Entity } from "./entity";
import { GameRoot, enumLayer } from "./root";
import { typeItemSingleton } from "./item_resolver";
import { enumLayer, GameRoot } from "./root";
const logger = createLogger("belt_path");
@ -29,7 +29,7 @@ export class BeltPath extends BasicSerializableObject {
static getSchema() {
return {
entityPath: types.array(types.entity),
items: types.array(types.pair(types.ufloat, types.obj(gItemRegistry))),
items: types.array(types.pair(types.ufloat, typeItemSingleton)),
spacingToFirstItem: types.ufloat,
};
}

View File

@ -2,6 +2,7 @@ import { gItemRegistry } from "../../core/global_registries";
import { types } from "../../savegame/serialization";
import { Component } from "../component";
import { BaseItem } from "../base_item";
import { typeItemSingleton } from "../item_resolver";
export class ConstantSignalComponent extends Component {
static getId() {
@ -10,7 +11,7 @@ export class ConstantSignalComponent extends Component {
static getSchema() {
return {
signal: types.nullable(types.obj(gItemRegistry)),
signal: types.nullable(typeItemSingleton),
};
}

View File

@ -1,11 +1,10 @@
import { Vector, enumDirection, enumDirectionToVector } from "../../core/vector";
import { BaseItem } from "../base_item";
import { Component } from "../component";
import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector";
import { types } from "../../savegame/serialization";
import { gItemRegistry } from "../../core/global_registries";
import { Entity } from "../entity";
import { enumLayer } from "../root";
import { BaseItem } from "../base_item";
import { BeltPath } from "../belt_path";
import { Component } from "../component";
import { Entity } from "../entity";
import { typeItemSingleton } from "../item_resolver";
/**
* @typedef {{
@ -29,7 +28,7 @@ export class ItemEjectorComponent extends Component {
return {
slots: types.array(
types.structured({
item: types.nullable(types.obj(gItemRegistry)),
item: types.nullable(typeItemSingleton),
progress: types.float,
})
),

View File

@ -1,7 +1,7 @@
import { gItemRegistry } from "../../core/global_registries";
import { types } from "../../savegame/serialization";
import { BaseItem } from "../base_item";
import { Component } from "../component";
import { typeItemSingleton } from "../item_resolver";
/** @enum {string} */
export const enumItemProcessorTypes = {
@ -32,13 +32,13 @@ export class ItemProcessorComponent extends Component {
nextOutputSlot: types.uint,
inputSlots: types.array(
types.structured({
item: types.obj(gItemRegistry),
item: typeItemSingleton,
sourceSlot: types.uint,
})
),
itemsToEject: types.array(
types.structured({
item: types.obj(gItemRegistry),
item: typeItemSingleton,
requiredSlot: types.nullable(types.uint),
preferredSlot: types.nullable(types.uint),
})

View File

@ -1,8 +1,7 @@
import { globalConfig } from "../../core/config";
import { types } from "../../savegame/serialization";
import { Component } from "../component";
import { BaseItem } from "../base_item";
import { gItemRegistry } from "../../core/global_registries";
import { Component } from "../component";
import { typeItemSingleton } from "../item_resolver";
const chainBufferSize = 3;
@ -15,7 +14,7 @@ export class MinerComponent extends Component {
// cachedMinedItem is not serialized.
return {
lastMiningTime: types.ufloat,
itemChainBuffer: types.array(types.obj(gItemRegistry)),
itemChainBuffer: types.array(typeItemSingleton),
};
}

View File

@ -1,9 +1,7 @@
import { Component } from "../component";
import { types } from "../../savegame/serialization";
import { gItemRegistry } from "../../core/global_registries";
import { BaseItem, enumItemType } from "../base_item";
import { ColorItem } from "../items/color_item";
import { ShapeItem } from "../items/shape_item";
import { Component } from "../component";
import { typeItemSingleton } from "../item_resolver";
export class StorageComponent extends Component {
static getId() {
@ -13,7 +11,7 @@ export class StorageComponent extends Component {
static getSchema() {
return {
storedCount: types.uint,
storedItem: types.nullable(types.obj(gItemRegistry)),
storedItem: types.nullable(typeItemSingleton),
};
}

View File

@ -1,10 +1,9 @@
import { BaseItem } from "../base_item";
import { Component } from "../component";
import { globalConfig } from "../../core/config";
import { types } from "../../savegame/serialization";
import { gItemRegistry } from "../../core/global_registries";
import { BaseItem } from "../base_item";
import { Component } from "../component";
import { Entity } from "../entity";
import { enumLayer } from "../root";
import { typeItemSingleton } from "../item_resolver";
/** @enum {string} */
export const enumUndergroundBeltMode = {
@ -26,7 +25,7 @@ export class UndergroundBeltComponent extends Component {
static getSchema() {
return {
pendingItems: types.array(types.pair(types.obj(gItemRegistry), types.float)),
pendingItems: types.array(types.pair(typeItemSingleton, types.float)),
};
}

View File

@ -13,7 +13,6 @@ import { randomInt, round2Digits } 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";
@ -67,7 +66,7 @@ export class GameCore {
/**
* Initializes the root object which stores all game related data. The state
* is required as a back reference (used sometimes)
* @param {InGameState} parentState
* @param {import("../states/ingame").InGameState} parentState
* @param {Savegame} savegame
*/
initializeRoot(parentState, savegame) {

View File

@ -0,0 +1,33 @@
import { types } from "../savegame/serialization";
import { gItemRegistry } from "../core/global_registries";
import { BooleanItem, BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "./items/boolean_item";
import { ShapeItem } from "./items/shape_item";
import { ColorItem, COLOR_ITEM_SINGLETONS } from "./items/color_item";
/**
* Resolves items so we share instances
* @param {import("../savegame/savegame_serializer").GameRoot} root
* @param {{$: string, data: any }} data
*/
export function itemResolverSingleton(root, data) {
const itemType = data.$;
const itemData = data.data;
switch (itemType) {
case BooleanItem.getId(): {
return itemData ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
}
case ShapeItem.getId(): {
return root.shapeDefinitionMgr.getShapeItemFromShortKey(itemData);
}
case ColorItem.getId(): {
return COLOR_ITEM_SINGLETONS[itemData];
}
default: {
assertAlways(false, "Unknown item type: " + itemType);
}
}
}
export const typeItemSingleton = types.obj(gItemRegistry, itemResolverSingleton);

View File

@ -98,3 +98,13 @@ export class ColorItem extends BaseItem {
context.fill();
}
}
/**
* Singleton instances
* @type {Object<enumColors, ColorItem>}
*/
export const COLOR_ITEM_SINGLETONS = {};
for (const color in enumColors) {
COLOR_ITEM_SINGLETONS[color] = new ColorItem(color);
}

View File

@ -37,7 +37,6 @@ export class ShapeItem extends BaseItem {
*/
constructor(definition) {
super();
// logger.log("New shape item for shape definition", definition.generateId(), "created");
/**
* This property must not be modified on runtime, you have to clone the class in order to change the definition

View File

@ -6,7 +6,7 @@ import { Vector } from "../core/vector";
import { BaseItem } from "./base_item";
import { enumColors } from "./colors";
import { Entity } from "./entity";
import { ColorItem } from "./items/color_item";
import { COLOR_ITEM_SINGLETONS } from "./items/color_item";
import { enumLayer, GameRoot } from "./root";
import { enumSubShape } from "./shape_definition";
@ -139,7 +139,7 @@ export class MapChunk {
if (distanceToOriginInChunks > 2) {
availableColors.push(enumColors.blue);
}
this.internalGeneratePatch(rng, colorPatchSize, new ColorItem(rng.choice(availableColors)));
this.internalGeneratePatch(rng, colorPatchSize, COLOR_ITEM_SINGLETONS[rng.choice(availableColors)]);
}
/**
@ -268,7 +268,7 @@ export class MapChunk {
*/
generatePredefined(rng) {
if (this.x === 0 && this.y === 0) {
this.internalGeneratePatch(rng, 2, new ColorItem(enumColors.red), 7, 7);
this.internalGeneratePatch(rng, 2, COLOR_ITEM_SINGLETONS[enumColors.red], 7, 7);
return true;
}
if (this.x === -1 && this.y === 0) {
@ -283,7 +283,7 @@ export class MapChunk {
}
if (this.x === -1 && this.y === -1) {
this.internalGeneratePatch(rng, 2, new ColorItem(enumColors.green));
this.internalGeneratePatch(rng, 2, COLOR_ITEM_SINGLETONS[enumColors.green]);
return true;
}

View File

@ -1,15 +1,14 @@
import { ConstantSignalComponent } from "../components/constant_signal";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { Entity } from "../entity";
import trim from "trim";
import { DialogWithForm } from "../../core/modal_dialog_elements";
import { FormElementInput } from "../../core/modal_dialog_forms";
import { enumColors } from "../colors";
import { ColorItem } from "../items/color_item";
import trim from "trim";
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
import { ShapeDefinition } from "../shape_definition";
import { ShapeItem } from "../items/shape_item";
import { BaseItem } from "../base_item";
import { enumColors } from "../colors";
import { ConstantSignalComponent } from "../components/constant_signal";
import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { ShapeDefinition } from "../shape_definition";
export class ConstantSignalSystem extends GameSystemWithFilter {
constructor(root) {
@ -111,7 +110,7 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
const codeLower = code.toLowerCase();
if (enumColors[codeLower]) {
return new ColorItem(codeLower);
return COLOR_ITEM_SINGLETONS[codeLower];
}
if (code === "1" || codeLower === "true") {
return BOOL_TRUE_SINGLETON;
@ -122,7 +121,7 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
}
if (ShapeDefinition.isValidShortKey(code)) {
return new ShapeItem(this.root.shapeDefinitionMgr.getShapeFromShortKey(code));
return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
}
return null;

View File

@ -1,11 +1,10 @@
import { GameSystemWithFilter } from "../game_system_with_filter";
import { HubComponent } from "../components/hub";
import { DrawParameters } from "../../core/draw_parameters";
import { Entity } from "../entity";
import { formatBigNumber } from "../../core/utils";
import { Loader } from "../../core/loader";
import { formatBigNumber } from "../../core/utils";
import { T } from "../../translations";
import { ShapeItem } from "../items/shape_item";
import { HubComponent } from "../components/hub";
import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
export class HubSystem extends GameSystemWithFilter {
constructor(root) {
@ -23,7 +22,9 @@ export class HubSystem extends GameSystemWithFilter {
// Set hub goal
const entity = this.allEntities[i];
const pinsComp = entity.components.WiredPins;
pinsComp.slots[0].value = new ShapeItem(this.root.hubGoals.currentGoal.definition);
pinsComp.slots[0].value = this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
this.root.hubGoals.currentGoal.definition
);
}
}

View File

@ -5,7 +5,7 @@ import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/it
import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { BOOL_TRUE_SINGLETON } from "../items/boolean_item";
import { ColorItem } from "../items/color_item";
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { ShapeItem } from "../items/shape_item";
export class ItemProcessorSystem extends GameSystemWithFilter {
@ -134,7 +134,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const definition = cutDefinitions[i];
if (!definition.isEntirelyEmpty()) {
outItems.push({
item: new ShapeItem(definition),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
requiredSlot: i,
});
}
@ -155,7 +155,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const definition = cutDefinitions[i];
if (!definition.isEntirelyEmpty()) {
outItems.push({
item: new ShapeItem(definition),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
requiredSlot: i,
});
}
@ -172,7 +172,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW(inputDefinition);
outItems.push({
item: new ShapeItem(rotatedDefinition),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
});
break;
}
@ -185,7 +185,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCCW(inputDefinition);
outItems.push({
item: new ShapeItem(rotatedDefinition),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
});
break;
}
@ -198,7 +198,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateFL(inputDefinition);
outItems.push({
item: new ShapeItem(rotatedDefinition),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
});
break;
}
@ -217,7 +217,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
upperItem.definition
);
outItems.push({
item: new ShapeItem(stackedDefinition),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(stackedDefinition),
});
break;
}
@ -248,7 +248,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
resultColor = mixedColor;
}
outItems.push({
item: new ColorItem(resultColor),
item: COLOR_ITEM_SINGLETONS[resultColor],
});
break;
@ -266,7 +266,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
);
outItems.push({
item: new ShapeItem(colorizedDefinition),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition),
});
break;
@ -293,11 +293,11 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
colorItem.color
);
outItems.push({
item: new ShapeItem(colorizedDefinition1),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition1),
});
outItems.push({
item: new ShapeItem(colorizedDefinition2),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition2),
});
break;
@ -324,7 +324,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
);
outItems.push({
item: new ShapeItem(colorizedDefinition),
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition),
});
break;

View File

@ -1,8 +1,11 @@
import { createLogger } from "../core/logging";
import {
BaseDataType,
TypeArray,
TypeBoolean,
TypeClass,
TypeClassData,
TypeClassFromMetaclass,
TypeClassId,
TypeEntity,
TypeEntityWeakref,
@ -17,12 +20,9 @@ import {
TypePositiveInteger,
TypePositiveNumber,
TypeString,
TypeVector,
TypeClassFromMetaclass,
TypeClassData,
TypeStructuredObject,
TypeVector,
} from "./serialization_data_types";
import { createLogger } from "../core/logging";
const logger = createLogger("serialization");
@ -69,9 +69,10 @@ export const types = {
/**
* @param {FactoryTemplate<*>} registry
* @param {(GameRoot, any) => object=} resolver
*/
obj(registry) {
return new TypeClass(registry);
obj(registry, resolver = null) {
return new TypeClass(registry, resolver);
},
/**
@ -190,12 +191,18 @@ export class BasicSerializableObject {
);
}
/** @returns {string|void} */
deserialize(data) {
/**
* @param {any} data
* @param {import("./savegame_serializer").GameRoot} root
* @returns {string|void}
*/
deserialize(data, root = null) {
return deserializeSchema(
this,
/** @type {typeof BasicSerializableObject} */ (this.constructor).getCachedSchema(),
data
data,
null,
root
);
}
@ -253,9 +260,10 @@ export function serializeSchema(obj, schema, mergeWith = {}) {
* @param {Schema} schema The schema to use
* @param {object} data The serialized data
* @param {string|void|null=} baseclassErrorResult Convenience, if this is a string error code, do nothing and return it
* @param {import("../game/root").GameRoot=} root Optional game root reference
* @returns {string|void} String error code or nothing on success
*/
export function deserializeSchema(obj, schema, data, baseclassErrorResult = null) {
export function deserializeSchema(obj, schema, data, baseclassErrorResult = null, root) {
if (baseclassErrorResult) {
return baseclassErrorResult;
}
@ -275,7 +283,7 @@ export function deserializeSchema(obj, schema, data, baseclassErrorResult = null
return "Non-nullable entry is null: " + key + " of class " + obj.constructor.name;
}
const errorStatus = schema[key].deserializeWithVerify(data[key], obj, key, obj.root);
const errorStatus = schema[key].deserializeWithVerify(data[key], obj, key, obj.root || root);
if (errorStatus) {
logger.error(
"Deserialization failed with error '" + errorStatus + "' on object",

View File

@ -595,10 +595,12 @@ export class TypeClass extends BaseDataType {
/**
*
* @param {FactoryTemplate<*>} registry
* @param {(GameRoot, object) => object} customResolver
*/
constructor(registry) {
constructor(registry, customResolver = null) {
super();
this.registry = registry;
this.customResolver = customResolver;
}
serialize(value) {
@ -640,14 +642,23 @@ export class TypeClass extends BaseDataType {
* @returns {string|void} String error code or null on success
*/
deserialize(value, targetObject, targetKey, root) {
const instanceClass = this.registry.findById(value.$);
if (!instanceClass || !instanceClass.prototype) {
return "Invalid class id (runtime-err): " + value.$ + "->" + instanceClass;
}
const instance = Object.create(instanceClass.prototype);
const errorState = instance.deserialize(value.data);
if (errorState) {
return errorState;
let instance;
if (this.customResolver) {
instance = this.customResolver(root, value);
if (!instance) {
return "Failed to call custom resolver";
}
} else {
const instanceClass = this.registry.findById(value.$);
if (!instanceClass || !instanceClass.prototype) {
return "Invalid class id (runtime-err): " + value.$ + "->" + instanceClass;
}
instance = Object.create(instanceClass.prototype);
const errorState = instance.deserialize(value.data);
if (errorState) {
return errorState;
}
}
targetObject[targetKey] = instance;
}

View File

@ -60,7 +60,7 @@ export class SerializerInternal {
entity.uid = payload.uid;
this.deserializeComponents(entity, payload.components);
this.deserializeComponents(root, entity, payload.components);
root.entityMgr.registerEntity(entity, payload.uid);
root.map.placeStaticEntity(entity);
@ -70,18 +70,19 @@ export class SerializerInternal {
/**
* Deserializes components of an entity
* @param {GameRoot} root
* @param {Entity} entity
* @param {Object.<string, any>} data
* @returns {string|void}
*/
deserializeComponents(entity, data) {
deserializeComponents(root, entity, data) {
for (const componentId in data) {
if (!entity.components[componentId]) {
logger.warn("Entity no longer has component:", componentId);
continue;
}
const errorStatus = entity.components[componentId].deserialize(data[componentId]);
const errorStatus = entity.components[componentId].deserialize(data[componentId], root);
if (errorStatus) {
return errorStatus;
}