diff --git a/src/Models/ThemeConfig/Conversion/Conversion.ts b/src/Models/ThemeConfig/Conversion/Conversion.ts index 84a67af867..5ad3f12c18 100644 --- a/src/Models/ThemeConfig/Conversion/Conversion.ts +++ b/src/Models/ThemeConfig/Conversion/Conversion.ts @@ -308,9 +308,30 @@ export class FirstOf extends Conversion { } } +export class Cached extends Conversion { + private _step: Conversion + private readonly key: string + constructor(step: Conversion) { + super("Secretly caches the output for the given input", [], "cached") + this._step = step + this.key = "__super_secret_caching_key_" + step.name + } + + convert(json: TIn, context: ConversionContext): TOut { + if (json[this.key]) { + return json[this.key] + } + const converted = this._step.convert(json, context) + Object.defineProperty(json, this.key, { + value: converted, + enumerable: false, + }) + return converted + } +} export class Fuse extends DesugaringStep { private readonly steps: DesugaringStep[] - + protected debug = false constructor(doc: string, ...steps: DesugaringStep[]) { super( (doc ?? "") + @@ -322,8 +343,15 @@ export class Fuse extends DesugaringStep { this.steps = Utils.NoNull(steps) } + public enableDebugging(): Fuse { + this.debug = true + return this + } + convert(json: T, context: ConversionContext): T { + const timings = [] for (let i = 0; i < this.steps.length; i++) { + const start = new Date() const step = this.steps[i] try { const r = step.convert(json, context.inOperation(step.name)) @@ -335,6 +363,14 @@ export class Fuse extends DesugaringStep { console.error("Step " + step.name + " failed due to ", e, e.stack) throw e } + if (this.debug) { + const stop = new Date() + const timeNeededMs = stop.getTime() - start.getTime() + timings.push(timeNeededMs) + } + } + if (this.debug) { + console.log("Time needed,", timings.join(", ")) } return json } diff --git a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts index 152b11437c..b75b6af2b3 100644 --- a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -1,4 +1,5 @@ import { + Cached, Concat, Conversion, ConversionContext, @@ -31,6 +32,7 @@ import { RenderingSpecification } from "../../../UI/SpecialVisualization" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import { ConfigMeta } from "../../../UI/Studio/configMeta" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" +import { j } from "vite-node/types-63205a44" class ExpandFilter extends DesugaringStep { private static readonly predefinedFilters = ExpandFilter.load_filters() @@ -483,14 +485,13 @@ export class AddQuestionBox extends DesugaringStep { ) { return json } - json = JSON.parse(JSON.stringify(json)) - const allSpecials: Exclude[] = [] - .concat( - ...json.tagRenderings.map((tr) => - ValidationUtils.getSpecialVisualsationsWithArgs(tr) - ) + json = { ...json } + json.tagRenderings = [...json.tagRenderings] + const allSpecials: Exclude[] = ( + ValidationUtils.getAllSpecialVisualisations(json.tagRenderings).filter( + (spec) => typeof spec !== "string" ) - .filter((spec) => typeof spec !== "string") + ) const questionSpecials = allSpecials.filter((sp) => sp.func.funcName === "questions") const noLabels = questionSpecials.filter( @@ -579,18 +580,34 @@ export class AddEditingElements extends DesugaringStep { if (this._desugaring.tagRenderings === null) { return json } - json = JSON.parse(JSON.stringify(json)) + if (json.source === "special") { + return json + } + if (!json.title && !json.tagRenderings) { + return json + } + json = { ...json } + json.tagRenderings = [...(json.tagRenderings ?? [])] + const specialVisualisations = ValidationUtils.getAllSpecialVisualisations( + json.tagRenderings + ) + const usedSpecialFunctions = new Set( + specialVisualisations.map((sv) => + typeof sv === "string" ? undefined : sv.func.funcName + ) + ) + if (!usedSpecialFunctions.has("minimap")) { + json.tagRenderings.push(this._desugaring.tagRenderings.get("minimap")) + } if ( - json.tagRenderings && this._desugaring.tagRenderings.has("just_created") && !json.tagRenderings.some((tr) => tr === "just_created" || tr["id"] === "just_created") ) { json.tagRenderings.unshift(this._desugaring.tagRenderings.get("just_created")) } - if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) { - json.tagRenderings ??= [] + if (json.allowSplit && !usedSpecialFunctions.has("split_button")) { json.tagRenderings.push({ id: "split-button", render: { "*": "{split_button()}" }, @@ -598,14 +615,13 @@ export class AddEditingElements extends DesugaringStep { delete json.allowSplit } - if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) { - json.tagRenderings ??= [] + if (json.allowMove && !usedSpecialFunctions.has("move_button")) { json.tagRenderings.push({ id: "move-button", render: { "*": "{move_button()}" }, }) } - if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "delete_button")) { + if (json.deletion && !usedSpecialFunctions.has("delete_button")) { json.tagRenderings.push({ id: "delete-button", render: { "*": "{delete_button()}" }, @@ -622,7 +638,7 @@ export class AddEditingElements extends DesugaringStep { json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit")) } - if (!ValidationUtils.hasSpecialVisualisation(json, "all_tags")) { + if (!usedSpecialFunctions.has("all_tags")) { const trc: QuestionableTagRenderingConfigJson = { id: "all-tags", render: { "*": "{all_tags()}" }, @@ -1141,41 +1157,6 @@ class SetFullNodeDatabase extends DesugaringStep { } } -export class AddMiniMap extends DesugaringStep { - private readonly _state: DesugaringContext - - constructor(state: DesugaringContext) { - super( - "Adds a default 'minimap'-element to the tagrenderings if none of the elements define such a minimap", - ["tagRenderings"], - "AddMiniMap" - ) - this._state = state - } - - convert(layerConfig: LayerConfigJson, context: ConversionContext): LayerConfigJson { - if (!layerConfig.tagRenderings || layerConfig.source === "special") { - return layerConfig - } - const state = this._state - const hasMinimap = ValidationUtils.hasSpecialVisualisation(layerConfig, "minimap") - if (!hasMinimap) { - layerConfig = { ...layerConfig } - layerConfig.tagRenderings = [...layerConfig.tagRenderings] - const minimap = state.tagRenderings.get("minimap") - if (minimap === undefined) { - if (state.tagRenderings.size > 0) { - throw "The 'minimap'-builtin tagrendering is not defined. As such, it cannot be added automatically" - } - } else { - layerConfig.tagRenderings.push(minimap) - } - } - - return layerConfig - } -} - class ExpandMarkerRenderings extends DesugaringStep { private readonly _layer: LayerConfigJson private readonly _state: DesugaringContext @@ -1211,16 +1192,15 @@ class ExpandMarkerRenderings extends DesugaringStep { } } -export class PrepareLayer extends Fuse { +export class PrepareLayer extends Cached { constructor(state: DesugaringContext) { - super( + const steps = new Fuse( "Fully prepares and expands a layer for the LayerConfig.", new On("tagRenderings", new Each(new RewriteSpecial())), new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), new On("tagRenderings", (layer) => new Concat(new ExpandTagRendering(state, layer))), new On("tagRenderings", new Each(new DetectInline())), new AddQuestionBox(), - new AddMiniMap(state), new AddEditingElements(state), new SetFullNodeDatabase(), new On< @@ -1244,5 +1224,6 @@ export class PrepareLayer extends Fuse { ), new ExpandFilter(state) ) + super(steps) } } diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 4d2e215900..cc85a78a42 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -275,8 +275,7 @@ export class ValidateThemeAndLayers extends Fuse { doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, - sharedTagRenderings?: Set, - msg?: string + sharedTagRenderings?: Set ) { super( "Validates a theme and the contained layers", @@ -287,8 +286,7 @@ export class ValidateThemeAndLayers extends Fuse { new Pipe( new ValidateLayer(undefined, isBuiltin, doesImageExist, false, true), new Pure((x) => x.raw) - ), - msg + ) ) ) ) diff --git a/src/Models/ThemeConfig/Conversion/ValidationUtils.ts b/src/Models/ThemeConfig/Conversion/ValidationUtils.ts index 26e3d05898..93c507aea5 100644 --- a/src/Models/ThemeConfig/Conversion/ValidationUtils.ts +++ b/src/Models/ThemeConfig/Conversion/ValidationUtils.ts @@ -2,25 +2,17 @@ import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" import { Utils } from "../../../Utils" import SpecialVisualizations from "../../../UI/SpecialVisualizations" import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization" -import { LayerConfigJson } from "../Json/LayerConfigJson" +import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" export default class ValidationUtils { - public static hasSpecialVisualisation( - layer: LayerConfigJson, - specialVisualisation: string - ): boolean { - return ( - layer.tagRenderings?.some((tagRendering) => { - if (tagRendering === undefined) { - return false - } - - const spec = ValidationUtils.getSpecialVisualisations( - tagRendering - ) - return spec.some((vis) => vis.funcName === specialVisualisation) - }) ?? false - ) + public static getAllSpecialVisualisations( + renderingConfigs: (TagRenderingConfigJson | QuestionableTagRenderingConfigJson)[] + ): RenderingSpecification[] { + const visualisations: RenderingSpecification[] = [] + for (const renderConfig of renderingConfigs) { + visualisations.push(...ValidationUtils.getSpecialVisualisationsWithArgs(renderConfig)) + } + return visualisations } /** @@ -30,14 +22,21 @@ export default class ValidationUtils { public static getSpecialVisualisations( renderingConfig: TagRenderingConfigJson ): SpecialVisualization[] { - return ValidationUtils.getSpecialVisualsationsWithArgs(renderingConfig).map( + return ValidationUtils.getSpecialVisualisationsWithArgs(renderingConfig).map( (spec) => spec["func"] ) } - public static getSpecialVisualsationsWithArgs( + public static getSpecialVisualisationsWithArgs( renderingConfig: TagRenderingConfigJson ): RenderingSpecification[] { + if (!renderingConfig) { + return [] + } + const cacheName = "__specialVisualisationsWithArgs_cache" + if (renderingConfig[cacheName]) { + return renderingConfig[cacheName] + } const translations: any[] = Utils.NoNull([ renderingConfig.render, ...(renderingConfig.mappings ?? []).map((m) => m.then), @@ -59,6 +58,15 @@ export default class ValidationUtils { all.push(...specials) } } + + // _Very_ dirty hack + Object.defineProperty(renderingConfig, cacheName, { + value: all, + enumerable: false, + configurable: true, + writable: true, + }) + return all } } diff --git a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts index 945953b696..11688e03ce 100644 --- a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts +++ b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts @@ -152,8 +152,6 @@ describe("PrepareTheme", () => { }, ], startLat: 0, - pointRendering: null, - lineRendering: null, startLon: 0, startZoom: 0, title: "Test theme",