From a81040d6a298e50b82f16d7437ecbef6e486c813 Mon Sep 17 00:00:00 2001
From: thepaperpilot <thepaperpilot@gmail.com>
Date: Sun, 27 Feb 2022 16:18:13 -0600
Subject: [PATCH] Moved persistence related code to its own file

---
 src/data/layers/aca/c.tsx                 |   3 +-
 src/data/layers/aca/f.tsx                 |   3 +-
 src/features/achievements/achievement.tsx |   4 +-
 src/features/boards/board.ts              |   5 +-
 src/features/buyable.tsx                  |   4 +-
 src/features/challenges/challenge.ts      |   3 +-
 src/features/feature.ts                   | 108 +---------------------
 src/features/grids/grid.ts                |   5 +-
 src/features/infoboxes/infobox.ts         |   4 +-
 src/features/milestones/milestone.tsx     |   4 +-
 src/features/resources/resource.ts        |   2 +-
 src/features/tabs/tabFamily.ts            |   4 +-
 src/features/trees/tree.ts                |   2 +-
 src/features/upgrades/upgrade.ts          |   8 +-
 src/game/layers.ts                        |  10 +-
 src/game/persistence.ts                   | 107 +++++++++++++++++++++
 16 files changed, 129 insertions(+), 147 deletions(-)
 create mode 100644 src/game/persistence.ts

diff --git a/src/data/layers/aca/c.tsx b/src/data/layers/aca/c.tsx
index ebb2357..fee6975 100644
--- a/src/data/layers/aca/c.tsx
+++ b/src/data/layers/aca/c.tsx
@@ -19,7 +19,7 @@ import {
     createCumulativeConversion,
     createExponentialScaling
 } from "@/features/conversion";
-import { jsx, persistent, showIf, Visibility } from "@/features/feature";
+import { jsx, showIf, Visibility } from "@/features/feature";
 import { createHotkey } from "@/features/hotkey";
 import { createInfobox } from "@/features/infoboxes/infobox";
 import { createMilestone } from "@/features/milestones/milestone";
@@ -32,6 +32,7 @@ import { createTabButton, createTabFamily } from "@/features/tabs/tabFamily";
 import { createTree, createTreeNode, GenericTreeNode, TreeBranch } from "@/features/trees/tree";
 import { createUpgrade } from "@/features/upgrades/upgrade";
 import { createLayer } from "@/game/layers";
+import { persistent } from "@/game/persistence";
 import settings from "@/game/settings";
 import { DecimalSource } from "@/lib/break_eternity";
 import Decimal, { format, formatWhole } from "@/util/bignum";
diff --git a/src/data/layers/aca/f.tsx b/src/data/layers/aca/f.tsx
index 416dfec..0aa7a90 100644
--- a/src/data/layers/aca/f.tsx
+++ b/src/data/layers/aca/f.tsx
@@ -2,12 +2,13 @@ import { createLayerTreeNode, createResetButton } from "@/data/common";
 import { main } from "@/data/mod";
 import { createClickable } from "@/features/clickables/clickable";
 import { createExponentialScaling, createIndependentConversion } from "@/features/conversion";
-import { jsx, persistent } from "@/features/feature";
+import { jsx } from "@/features/feature";
 import { createInfobox } from "@/features/infoboxes/infobox";
 import { createReset } from "@/features/reset";
 import MainDisplay from "@/features/resources/MainDisplay.vue";
 import { createResource, displayResource } from "@/features/resources/resource";
 import { createLayer } from "@/game/layers";
+import { persistent } from "@/game/persistence";
 import Decimal, { DecimalSource, formatWhole } from "@/util/bignum";
 import { render } from "@/util/vue";
 import c from "./c";
diff --git a/src/features/achievements/achievement.tsx b/src/features/achievements/achievement.tsx
index 27279e3..cd9e403 100644
--- a/src/features/achievements/achievement.tsx
+++ b/src/features/achievements/achievement.tsx
@@ -5,9 +5,6 @@ import {
     findFeatures,
     GatherProps,
     getUniqueID,
-    makePersistent,
-    Persistent,
-    PersistentState,
     Replace,
     setDefault,
     StyleValue,
@@ -15,6 +12,7 @@ import {
 } from "@/features/feature";
 import { globalBus } from "@/game/events";
 import "@/game/notifications";
+import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
 import {
     Computable,
     GetComputableType,
diff --git a/src/features/boards/board.ts b/src/features/boards/board.ts
index 2833721..1681011 100644
--- a/src/features/boards/board.ts
+++ b/src/features/boards/board.ts
@@ -4,16 +4,13 @@ import {
     findFeatures,
     GatherProps,
     getUniqueID,
-    makePersistent,
-    Persistent,
-    PersistentState,
     Replace,
     setDefault,
-    State,
     StyleValue,
     Visibility
 } from "@/features/feature";
 import { globalBus } from "@/game/events";
+import { State, Persistent, makePersistent, PersistentState } from "@/game/persistence";
 import Decimal, { DecimalSource } from "@/lib/break_eternity";
 import { isFunction } from "@/util/common";
 import {
diff --git a/src/features/buyable.tsx b/src/features/buyable.tsx
index 3df26e6..33ab2f9 100644
--- a/src/features/buyable.tsx
+++ b/src/features/buyable.tsx
@@ -1,5 +1,6 @@
 import ClickableComponent from "@/features/clickables/Clickable.vue";
 import { Resource } from "@/features/resources/resource";
+import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
 import Decimal, { DecimalSource, format, formatWhole } from "@/util/bignum";
 import {
     Computable,
@@ -17,9 +18,6 @@ import {
     GatherProps,
     getUniqueID,
     jsx,
-    makePersistent,
-    Persistent,
-    PersistentState,
     Replace,
     setDefault,
     StyleValue,
diff --git a/src/features/challenges/challenge.ts b/src/features/challenges/challenge.ts
index c8e9740..6406958 100644
--- a/src/features/challenges/challenge.ts
+++ b/src/features/challenges/challenge.ts
@@ -4,8 +4,6 @@ import {
     Component,
     GatherProps,
     getUniqueID,
-    persistent,
-    PersistentRef,
     Replace,
     setDefault,
     StyleValue,
@@ -14,6 +12,7 @@ import {
 import { GenericReset } from "@/features/reset";
 import { Resource } from "@/features/resources/resource";
 import { globalBus } from "@/game/events";
+import { PersistentRef, persistent } from "@/game/persistence";
 import settings from "@/game/settings";
 import Decimal, { DecimalSource } from "@/util/bignum";
 import {
diff --git a/src/features/feature.ts b/src/features/feature.ts
index 553e60e..2310dbe 100644
--- a/src/features/feature.ts
+++ b/src/features/feature.ts
@@ -1,37 +1,15 @@
-import { globalBus } from "@/game/events";
-import { GenericLayer } from "@/game/layers";
-import Decimal, { DecimalSource } from "@/util/bignum";
+import { DefaultValue } from "@/game/persistence";
+import Decimal from "@/util/bignum";
 import { DoNotCache, ProcessedComputable } from "@/util/computed";
-import { ProxyState } from "@/util/proxies";
-import { isArray } from "@vue/shared";
-import { CSSProperties, DefineComponent, isRef, ref, Ref } from "vue";
+import { CSSProperties, DefineComponent, isRef } from "vue";
 
-export const PersistentState = Symbol("PersistentState");
-export const DefaultValue = Symbol("DefaultValue");
 export const Component = Symbol("Component");
 export const GatherProps = Symbol("GatherProps");
 
-// Note: This is a union of things that should be safely stringifiable without needing
-// special processes for knowing what to load them in as
-// - Decimals aren't allowed because we'd need to know to parse them back.
-// - DecimalSources are allowed because the string is a valid value for them
-export type State =
-    | string
-    | number
-    | boolean
-    | DecimalSource
-    | { [key: string]: State }
-    | { [key: number]: State };
 export type JSXFunction = (() => JSX.Element) & { [DoNotCache]: true };
 export type CoercableComponent = string | DefineComponent | JSXFunction;
 export type StyleValue = string | CSSProperties | Array<string | CSSProperties>;
 
-export type Persistent<T extends State = State> = {
-    [PersistentState]: Ref<T>;
-    [DefaultValue]: T;
-};
-export type PersistentRef<T extends State = State> = Ref<T> & Persistent<T>;
-
 // TODO if importing .vue components in .tsx can become type safe,
 // this type can probably be safely removed
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -70,27 +48,6 @@ export function showIf(condition: boolean, otherwise = Visibility.None): Visibil
     return condition ? Visibility.Visible : otherwise;
 }
 
-export function persistent<T extends State>(defaultValue: T | Ref<T>): PersistentRef<T> {
-    const persistent = (
-        isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
-    ) as PersistentRef<T>;
-
-    persistent[PersistentState] = persistent;
-    persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
-    return persistent as PersistentRef<T>;
-}
-
-export function makePersistent<T extends State>(
-    obj: unknown,
-    defaultValue: T
-): asserts obj is Persistent<T> {
-    const persistent = obj as Partial<Persistent<T>>;
-    const state = ref(defaultValue) as Ref<T>;
-
-    persistent[PersistentState] = state;
-    persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue;
-}
-
 export function setDefault<T, K extends keyof T>(
     object: T,
     key: K,
@@ -118,62 +75,3 @@ export function findFeatures(obj: Record<string, unknown>, type: symbol): unknow
     handleObject(obj);
     return objects;
 }
-
-globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => {
-    const handleObject = (obj: Record<string, unknown>, path: string[] = []): boolean => {
-        let foundPersistent = false;
-        Object.keys(obj).forEach(key => {
-            const value = obj[key];
-            if (value && typeof value === "object") {
-                if (PersistentState in value) {
-                    foundPersistent = true;
-
-                    // Construct save path if it doesn't exist
-                    const persistentState = path.reduce<Record<string, unknown>>((acc, curr) => {
-                        if (!(curr in acc)) {
-                            acc[curr] = {};
-                        }
-                        return acc[curr] as Record<string, unknown>;
-                    }, saveData);
-
-                    // Cache currently saved value
-                    const savedValue = persistentState[key];
-                    // Add ref to save data
-                    persistentState[key] = (value as Persistent)[PersistentState];
-                    // Load previously saved value
-                    if (savedValue != null) {
-                        (persistentState[key] as Ref<unknown>).value = savedValue;
-                    } else {
-                        (persistentState[key] as Ref<unknown>).value = (value as Persistent)[
-                            DefaultValue
-                        ];
-                    }
-                } else if (!(value instanceof Decimal) && !isRef(value)) {
-                    // Continue traversing
-                    const foundPersistentInChild = handleObject(value as Record<string, unknown>, [
-                        ...path,
-                        key
-                    ]);
-
-                    // Show warning for persistent values inside arrays
-                    // TODO handle arrays better
-                    if (foundPersistentInChild) {
-                        if (isArray(value) && !isArray(obj)) {
-                            console.warn(
-                                "Found array that contains persistent values when adding layer. Keep in mind changing the order of elements in the array will mess with existing player saves.",
-                                ProxyState in obj
-                                    ? (obj as Record<PropertyKey, unknown>)[ProxyState]
-                                    : obj,
-                                key
-                            );
-                        } else {
-                            foundPersistent = true;
-                        }
-                    }
-                }
-            }
-        });
-        return foundPersistent;
-    };
-    handleObject(layer);
-});
diff --git a/src/features/grids/grid.ts b/src/features/grids/grid.ts
index 8081642..3c6ab97 100644
--- a/src/features/grids/grid.ts
+++ b/src/features/grids/grid.ts
@@ -4,12 +4,8 @@ import {
     Component,
     GatherProps,
     getUniqueID,
-    makePersistent,
-    Persistent,
-    PersistentState,
     Replace,
     setDefault,
-    State,
     StyleValue,
     Visibility
 } from "@/features/feature";
@@ -23,6 +19,7 @@ import {
 } from "@/util/computed";
 import { createLazyProxy } from "@/util/proxies";
 import { computed, Ref, unref } from "vue";
+import { State, Persistent, makePersistent, PersistentState } from "@/game/persistence";
 
 export const GridType = Symbol("Grid");
 
diff --git a/src/features/infoboxes/infobox.ts b/src/features/infoboxes/infobox.ts
index d86a22e..226b40e 100644
--- a/src/features/infoboxes/infobox.ts
+++ b/src/features/infoboxes/infobox.ts
@@ -4,9 +4,6 @@ import {
     Component,
     GatherProps,
     getUniqueID,
-    makePersistent,
-    Persistent,
-    PersistentState,
     Replace,
     setDefault,
     StyleValue,
@@ -21,6 +18,7 @@ import {
 } from "@/util/computed";
 import { createLazyProxy } from "@/util/proxies";
 import { Ref } from "vue";
+import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
 
 export const InfoboxType = Symbol("Infobox");
 
diff --git a/src/features/milestones/milestone.tsx b/src/features/milestones/milestone.tsx
index ea9a3c8..31819fa 100644
--- a/src/features/milestones/milestone.tsx
+++ b/src/features/milestones/milestone.tsx
@@ -5,9 +5,6 @@ import {
     findFeatures,
     GatherProps,
     getUniqueID,
-    makePersistent,
-    Persistent,
-    PersistentState,
     Replace,
     setDefault,
     StyleValue,
@@ -28,6 +25,7 @@ import { coerceComponent, isCoercableComponent } from "@/util/vue";
 import { Unsubscribe } from "nanoevents";
 import { computed, Ref, unref } from "vue";
 import { useToast } from "vue-toastification";
+import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
 
 export const MilestoneType = Symbol("Milestone");
 
diff --git a/src/features/resources/resource.ts b/src/features/resources/resource.ts
index 42ba77e..c1f956a 100644
--- a/src/features/resources/resource.ts
+++ b/src/features/resources/resource.ts
@@ -1,7 +1,7 @@
-import { persistent, State } from "@/features/feature";
 import Decimal, { DecimalSource, format, formatWhole } from "@/util/bignum";
 import { computed, ComputedRef, ref, Ref, watch } from "vue";
 import { globalBus } from "@/game/events";
+import { State, persistent } from "@/game/persistence";
 
 export interface Resource<T = DecimalSource> extends Ref<T> {
     displayName: string;
diff --git a/src/features/tabs/tabFamily.ts b/src/features/tabs/tabFamily.ts
index 871f7bd..c92d428 100644
--- a/src/features/tabs/tabFamily.ts
+++ b/src/features/tabs/tabFamily.ts
@@ -3,9 +3,6 @@ import {
     Component,
     GatherProps,
     getUniqueID,
-    makePersistent,
-    Persistent,
-    PersistentState,
     Replace,
     setDefault,
     StyleValue,
@@ -13,6 +10,7 @@ import {
 } from "@/features/feature";
 import TabButtonComponent from "@/features/tabs/TabButton.vue";
 import TabFamilyComponent from "@/features/tabs/TabFamily.vue";
+import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
 import {
     Computable,
     GetComputableType,
diff --git a/src/features/trees/tree.ts b/src/features/trees/tree.ts
index 8b634e9..e9be03b 100644
--- a/src/features/trees/tree.ts
+++ b/src/features/trees/tree.ts
@@ -3,7 +3,6 @@ import {
     Component,
     GatherProps,
     getUniqueID,
-    persistent,
     Replace,
     setDefault,
     StyleValue,
@@ -14,6 +13,7 @@ import { GenericReset } from "@/features/reset";
 import { displayResource, Resource } from "@/features/resources/resource";
 import { Tooltip } from "@/features/tooltip";
 import TreeComponent from "@/features/trees/Tree.vue";
+import { persistent } from "@/game/persistence";
 import { DecimalSource, format } from "@/util/bignum";
 import Decimal, { formatWhole } from "@/util/break_eternity";
 import {
diff --git a/src/features/upgrades/upgrade.ts b/src/features/upgrades/upgrade.ts
index 1036c9a..a9f96c2 100644
--- a/src/features/upgrades/upgrade.ts
+++ b/src/features/upgrades/upgrade.ts
@@ -5,9 +5,6 @@ import {
     findFeatures,
     GatherProps,
     getUniqueID,
-    makePersistent,
-    Persistent,
-    PersistentState,
     Replace,
     setDefault,
     StyleValue,
@@ -26,6 +23,7 @@ import {
 } from "@/util/computed";
 import { createLazyProxy } from "@/util/proxies";
 import { computed, Ref, unref } from "vue";
+import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
 
 export const UpgradeType = Symbol("Upgrade");
 
@@ -89,9 +87,9 @@ export function createUpgrade<T extends UpgradeOptions>(
         upgrade.type = UpgradeType;
         upgrade[Component] = UpgradeComponent;
 
-        if (upgrade.canPurchase == null && (upgrade.resource == null || upgrade.cost == null)) {
+        if (upgrade.canAfford == null && (upgrade.resource == null || upgrade.cost == null)) {
             console.warn(
-                "Error: can't create upgrade without a canPurchase property or a resource and cost property",
+                "Error: can't create upgrade without a canAfford property or a resource and cost property",
                 upgrade
             );
         }
diff --git a/src/game/layers.ts b/src/game/layers.ts
index 0d634b9..90e2bf5 100644
--- a/src/game/layers.ts
+++ b/src/game/layers.ts
@@ -1,11 +1,4 @@
-import {
-    CoercableComponent,
-    persistent,
-    PersistentRef,
-    Replace,
-    setDefault,
-    StyleValue
-} from "@/features/feature";
+import { CoercableComponent, Replace, setDefault, StyleValue } from "@/features/feature";
 import { Link } from "@/features/links";
 import Decimal from "@/util/bignum";
 import {
@@ -18,6 +11,7 @@ import {
 import { createLazyProxy } from "@/util/proxies";
 import { createNanoEvents, Emitter } from "nanoevents";
 import { globalBus } from "./events";
+import { persistent, PersistentRef } from "./persistence";
 import player from "./player";
 
 export interface LayerEvents {
diff --git a/src/game/persistence.ts b/src/game/persistence.ts
new file mode 100644
index 0000000..f8d2e80
--- /dev/null
+++ b/src/game/persistence.ts
@@ -0,0 +1,107 @@
+import { globalBus } from "@/game/events";
+import Decimal, { DecimalSource } from "@/util/bignum";
+import { ProxyState } from "@/util/proxies";
+import { isArray } from "@vue/shared";
+import { isRef, Ref, ref } from "vue";
+import { GenericLayer } from "./layers";
+
+export const PersistentState = Symbol("PersistentState");
+export const DefaultValue = Symbol("DefaultValue");
+
+// Note: This is a union of things that should be safely stringifiable without needing
+// special processes for knowing what to load them in as
+// - Decimals aren't allowed because we'd need to know to parse them back.
+// - DecimalSources are allowed because the string is a valid value for them
+export type State =
+    | string
+    | number
+    | boolean
+    | DecimalSource
+    | { [key: string]: State }
+    | { [key: number]: State };
+
+export type Persistent<T extends State = State> = {
+    [PersistentState]: Ref<T>;
+    [DefaultValue]: T;
+};
+export type PersistentRef<T extends State = State> = Ref<T> & Persistent<T>;
+
+export function persistent<T extends State>(defaultValue: T | Ref<T>): PersistentRef<T> {
+    const persistent = (
+        isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
+    ) as PersistentRef<T>;
+
+    persistent[PersistentState] = persistent;
+    persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
+    return persistent as PersistentRef<T>;
+}
+
+export function makePersistent<T extends State>(
+    obj: unknown,
+    defaultValue: T
+): asserts obj is Persistent<T> {
+    const persistent = obj as Partial<Persistent<T>>;
+    const state = ref(defaultValue) as Ref<T>;
+
+    persistent[PersistentState] = state;
+    persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue;
+}
+
+globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => {
+    const handleObject = (obj: Record<string, unknown>, path: string[] = []): boolean => {
+        let foundPersistent = false;
+        Object.keys(obj).forEach(key => {
+            const value = obj[key];
+            if (value && typeof value === "object") {
+                if (PersistentState in value) {
+                    foundPersistent = true;
+
+                    // Construct save path if it doesn't exist
+                    const persistentState = path.reduce<Record<string, unknown>>((acc, curr) => {
+                        if (!(curr in acc)) {
+                            acc[curr] = {};
+                        }
+                        return acc[curr] as Record<string, unknown>;
+                    }, saveData);
+
+                    // Cache currently saved value
+                    const savedValue = persistentState[key];
+                    // Add ref to save data
+                    persistentState[key] = (value as Persistent)[PersistentState];
+                    // Load previously saved value
+                    if (savedValue != null) {
+                        (persistentState[key] as Ref<unknown>).value = savedValue;
+                    } else {
+                        (persistentState[key] as Ref<unknown>).value = (value as Persistent)[
+                            DefaultValue
+                        ];
+                    }
+                } else if (!(value instanceof Decimal) && !isRef(value)) {
+                    // Continue traversing
+                    const foundPersistentInChild = handleObject(value as Record<string, unknown>, [
+                        ...path,
+                        key
+                    ]);
+
+                    // Show warning for persistent values inside arrays
+                    // TODO handle arrays better
+                    if (foundPersistentInChild) {
+                        if (isArray(value) && !isArray(obj)) {
+                            console.warn(
+                                "Found array that contains persistent values when adding layer. Keep in mind changing the order of elements in the array will mess with existing player saves.",
+                                ProxyState in obj
+                                    ? (obj as Record<PropertyKey, unknown>)[ProxyState]
+                                    : obj,
+                                key
+                            );
+                        } else {
+                            foundPersistent = true;
+                        }
+                    }
+                }
+            }
+        });
+        return foundPersistent;
+    };
+    handleObject(layer);
+});