Moved persistence related code to its own file
This commit is contained in:
parent
a8af84f581
commit
a81040d6a2
16 changed files with 129 additions and 147 deletions
|
@ -19,7 +19,7 @@ import {
|
||||||
createCumulativeConversion,
|
createCumulativeConversion,
|
||||||
createExponentialScaling
|
createExponentialScaling
|
||||||
} from "@/features/conversion";
|
} from "@/features/conversion";
|
||||||
import { jsx, persistent, showIf, Visibility } from "@/features/feature";
|
import { jsx, showIf, Visibility } from "@/features/feature";
|
||||||
import { createHotkey } from "@/features/hotkey";
|
import { createHotkey } from "@/features/hotkey";
|
||||||
import { createInfobox } from "@/features/infoboxes/infobox";
|
import { createInfobox } from "@/features/infoboxes/infobox";
|
||||||
import { createMilestone } from "@/features/milestones/milestone";
|
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 { createTree, createTreeNode, GenericTreeNode, TreeBranch } from "@/features/trees/tree";
|
||||||
import { createUpgrade } from "@/features/upgrades/upgrade";
|
import { createUpgrade } from "@/features/upgrades/upgrade";
|
||||||
import { createLayer } from "@/game/layers";
|
import { createLayer } from "@/game/layers";
|
||||||
|
import { persistent } from "@/game/persistence";
|
||||||
import settings from "@/game/settings";
|
import settings from "@/game/settings";
|
||||||
import { DecimalSource } from "@/lib/break_eternity";
|
import { DecimalSource } from "@/lib/break_eternity";
|
||||||
import Decimal, { format, formatWhole } from "@/util/bignum";
|
import Decimal, { format, formatWhole } from "@/util/bignum";
|
||||||
|
|
|
@ -2,12 +2,13 @@ import { createLayerTreeNode, createResetButton } from "@/data/common";
|
||||||
import { main } from "@/data/mod";
|
import { main } from "@/data/mod";
|
||||||
import { createClickable } from "@/features/clickables/clickable";
|
import { createClickable } from "@/features/clickables/clickable";
|
||||||
import { createExponentialScaling, createIndependentConversion } from "@/features/conversion";
|
import { createExponentialScaling, createIndependentConversion } from "@/features/conversion";
|
||||||
import { jsx, persistent } from "@/features/feature";
|
import { jsx } from "@/features/feature";
|
||||||
import { createInfobox } from "@/features/infoboxes/infobox";
|
import { createInfobox } from "@/features/infoboxes/infobox";
|
||||||
import { createReset } from "@/features/reset";
|
import { createReset } from "@/features/reset";
|
||||||
import MainDisplay from "@/features/resources/MainDisplay.vue";
|
import MainDisplay from "@/features/resources/MainDisplay.vue";
|
||||||
import { createResource, displayResource } from "@/features/resources/resource";
|
import { createResource, displayResource } from "@/features/resources/resource";
|
||||||
import { createLayer } from "@/game/layers";
|
import { createLayer } from "@/game/layers";
|
||||||
|
import { persistent } from "@/game/persistence";
|
||||||
import Decimal, { DecimalSource, formatWhole } from "@/util/bignum";
|
import Decimal, { DecimalSource, formatWhole } from "@/util/bignum";
|
||||||
import { render } from "@/util/vue";
|
import { render } from "@/util/vue";
|
||||||
import c from "./c";
|
import c from "./c";
|
||||||
|
|
|
@ -5,9 +5,6 @@ import {
|
||||||
findFeatures,
|
findFeatures,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
makePersistent,
|
|
||||||
Persistent,
|
|
||||||
PersistentState,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
|
@ -15,6 +12,7 @@ import {
|
||||||
} from "@/features/feature";
|
} from "@/features/feature";
|
||||||
import { globalBus } from "@/game/events";
|
import { globalBus } from "@/game/events";
|
||||||
import "@/game/notifications";
|
import "@/game/notifications";
|
||||||
|
import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
|
|
@ -4,16 +4,13 @@ import {
|
||||||
findFeatures,
|
findFeatures,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
makePersistent,
|
|
||||||
Persistent,
|
|
||||||
PersistentState,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
State,
|
|
||||||
StyleValue,
|
StyleValue,
|
||||||
Visibility
|
Visibility
|
||||||
} from "@/features/feature";
|
} from "@/features/feature";
|
||||||
import { globalBus } from "@/game/events";
|
import { globalBus } from "@/game/events";
|
||||||
|
import { State, Persistent, makePersistent, PersistentState } from "@/game/persistence";
|
||||||
import Decimal, { DecimalSource } from "@/lib/break_eternity";
|
import Decimal, { DecimalSource } from "@/lib/break_eternity";
|
||||||
import { isFunction } from "@/util/common";
|
import { isFunction } from "@/util/common";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import ClickableComponent from "@/features/clickables/Clickable.vue";
|
import ClickableComponent from "@/features/clickables/Clickable.vue";
|
||||||
import { Resource } from "@/features/resources/resource";
|
import { Resource } from "@/features/resources/resource";
|
||||||
|
import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
|
||||||
import Decimal, { DecimalSource, format, formatWhole } from "@/util/bignum";
|
import Decimal, { DecimalSource, format, formatWhole } from "@/util/bignum";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -17,9 +18,6 @@ import {
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
jsx,
|
jsx,
|
||||||
makePersistent,
|
|
||||||
Persistent,
|
|
||||||
PersistentState,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
|
|
|
@ -4,8 +4,6 @@ import {
|
||||||
Component,
|
Component,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
persistent,
|
|
||||||
PersistentRef,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
|
@ -14,6 +12,7 @@ import {
|
||||||
import { GenericReset } from "@/features/reset";
|
import { GenericReset } from "@/features/reset";
|
||||||
import { Resource } from "@/features/resources/resource";
|
import { Resource } from "@/features/resources/resource";
|
||||||
import { globalBus } from "@/game/events";
|
import { globalBus } from "@/game/events";
|
||||||
|
import { PersistentRef, persistent } from "@/game/persistence";
|
||||||
import settings from "@/game/settings";
|
import settings from "@/game/settings";
|
||||||
import Decimal, { DecimalSource } from "@/util/bignum";
|
import Decimal, { DecimalSource } from "@/util/bignum";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,37 +1,15 @@
|
||||||
import { globalBus } from "@/game/events";
|
import { DefaultValue } from "@/game/persistence";
|
||||||
import { GenericLayer } from "@/game/layers";
|
import Decimal from "@/util/bignum";
|
||||||
import Decimal, { DecimalSource } from "@/util/bignum";
|
|
||||||
import { DoNotCache, ProcessedComputable } from "@/util/computed";
|
import { DoNotCache, ProcessedComputable } from "@/util/computed";
|
||||||
import { ProxyState } from "@/util/proxies";
|
import { CSSProperties, DefineComponent, isRef } from "vue";
|
||||||
import { isArray } from "@vue/shared";
|
|
||||||
import { CSSProperties, DefineComponent, isRef, ref, Ref } from "vue";
|
|
||||||
|
|
||||||
export const PersistentState = Symbol("PersistentState");
|
|
||||||
export const DefaultValue = Symbol("DefaultValue");
|
|
||||||
export const Component = Symbol("Component");
|
export const Component = Symbol("Component");
|
||||||
export const GatherProps = Symbol("GatherProps");
|
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 JSXFunction = (() => JSX.Element) & { [DoNotCache]: true };
|
||||||
export type CoercableComponent = string | DefineComponent | JSXFunction;
|
export type CoercableComponent = string | DefineComponent | JSXFunction;
|
||||||
export type StyleValue = string | CSSProperties | Array<string | CSSProperties>;
|
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,
|
// TODO if importing .vue components in .tsx can become type safe,
|
||||||
// this type can probably be safely removed
|
// this type can probably be safely removed
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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;
|
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>(
|
export function setDefault<T, K extends keyof T>(
|
||||||
object: T,
|
object: T,
|
||||||
key: K,
|
key: K,
|
||||||
|
@ -118,62 +75,3 @@ export function findFeatures(obj: Record<string, unknown>, type: symbol): unknow
|
||||||
handleObject(obj);
|
handleObject(obj);
|
||||||
return objects;
|
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);
|
|
||||||
});
|
|
||||||
|
|
|
@ -4,12 +4,8 @@ import {
|
||||||
Component,
|
Component,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
makePersistent,
|
|
||||||
Persistent,
|
|
||||||
PersistentState,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
State,
|
|
||||||
StyleValue,
|
StyleValue,
|
||||||
Visibility
|
Visibility
|
||||||
} from "@/features/feature";
|
} from "@/features/feature";
|
||||||
|
@ -23,6 +19,7 @@ import {
|
||||||
} from "@/util/computed";
|
} from "@/util/computed";
|
||||||
import { createLazyProxy } from "@/util/proxies";
|
import { createLazyProxy } from "@/util/proxies";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
|
import { State, Persistent, makePersistent, PersistentState } from "@/game/persistence";
|
||||||
|
|
||||||
export const GridType = Symbol("Grid");
|
export const GridType = Symbol("Grid");
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,6 @@ import {
|
||||||
Component,
|
Component,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
makePersistent,
|
|
||||||
Persistent,
|
|
||||||
PersistentState,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
|
@ -21,6 +18,7 @@ import {
|
||||||
} from "@/util/computed";
|
} from "@/util/computed";
|
||||||
import { createLazyProxy } from "@/util/proxies";
|
import { createLazyProxy } from "@/util/proxies";
|
||||||
import { Ref } from "vue";
|
import { Ref } from "vue";
|
||||||
|
import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
|
||||||
|
|
||||||
export const InfoboxType = Symbol("Infobox");
|
export const InfoboxType = Symbol("Infobox");
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,6 @@ import {
|
||||||
findFeatures,
|
findFeatures,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
makePersistent,
|
|
||||||
Persistent,
|
|
||||||
PersistentState,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
|
@ -28,6 +25,7 @@ import { coerceComponent, isCoercableComponent } from "@/util/vue";
|
||||||
import { Unsubscribe } from "nanoevents";
|
import { Unsubscribe } from "nanoevents";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
|
import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
|
||||||
|
|
||||||
export const MilestoneType = Symbol("Milestone");
|
export const MilestoneType = Symbol("Milestone");
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { persistent, State } from "@/features/feature";
|
|
||||||
import Decimal, { DecimalSource, format, formatWhole } from "@/util/bignum";
|
import Decimal, { DecimalSource, format, formatWhole } from "@/util/bignum";
|
||||||
import { computed, ComputedRef, ref, Ref, watch } from "vue";
|
import { computed, ComputedRef, ref, Ref, watch } from "vue";
|
||||||
import { globalBus } from "@/game/events";
|
import { globalBus } from "@/game/events";
|
||||||
|
import { State, persistent } from "@/game/persistence";
|
||||||
|
|
||||||
export interface Resource<T = DecimalSource> extends Ref<T> {
|
export interface Resource<T = DecimalSource> extends Ref<T> {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
|
|
@ -3,9 +3,6 @@ import {
|
||||||
Component,
|
Component,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
makePersistent,
|
|
||||||
Persistent,
|
|
||||||
PersistentState,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
|
@ -13,6 +10,7 @@ import {
|
||||||
} from "@/features/feature";
|
} from "@/features/feature";
|
||||||
import TabButtonComponent from "@/features/tabs/TabButton.vue";
|
import TabButtonComponent from "@/features/tabs/TabButton.vue";
|
||||||
import TabFamilyComponent from "@/features/tabs/TabFamily.vue";
|
import TabFamilyComponent from "@/features/tabs/TabFamily.vue";
|
||||||
|
import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
Component,
|
Component,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
persistent,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
|
@ -14,6 +13,7 @@ import { GenericReset } from "@/features/reset";
|
||||||
import { displayResource, Resource } from "@/features/resources/resource";
|
import { displayResource, Resource } from "@/features/resources/resource";
|
||||||
import { Tooltip } from "@/features/tooltip";
|
import { Tooltip } from "@/features/tooltip";
|
||||||
import TreeComponent from "@/features/trees/Tree.vue";
|
import TreeComponent from "@/features/trees/Tree.vue";
|
||||||
|
import { persistent } from "@/game/persistence";
|
||||||
import { DecimalSource, format } from "@/util/bignum";
|
import { DecimalSource, format } from "@/util/bignum";
|
||||||
import Decimal, { formatWhole } from "@/util/break_eternity";
|
import Decimal, { formatWhole } from "@/util/break_eternity";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -5,9 +5,6 @@ import {
|
||||||
findFeatures,
|
findFeatures,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
makePersistent,
|
|
||||||
Persistent,
|
|
||||||
PersistentState,
|
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
|
@ -26,6 +23,7 @@ import {
|
||||||
} from "@/util/computed";
|
} from "@/util/computed";
|
||||||
import { createLazyProxy } from "@/util/proxies";
|
import { createLazyProxy } from "@/util/proxies";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
|
import { Persistent, makePersistent, PersistentState } from "@/game/persistence";
|
||||||
|
|
||||||
export const UpgradeType = Symbol("Upgrade");
|
export const UpgradeType = Symbol("Upgrade");
|
||||||
|
|
||||||
|
@ -89,9 +87,9 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
upgrade.type = UpgradeType;
|
upgrade.type = UpgradeType;
|
||||||
upgrade[Component] = UpgradeComponent;
|
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(
|
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
|
upgrade
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { CoercableComponent, Replace, setDefault, StyleValue } from "@/features/feature";
|
||||||
CoercableComponent,
|
|
||||||
persistent,
|
|
||||||
PersistentRef,
|
|
||||||
Replace,
|
|
||||||
setDefault,
|
|
||||||
StyleValue
|
|
||||||
} from "@/features/feature";
|
|
||||||
import { Link } from "@/features/links";
|
import { Link } from "@/features/links";
|
||||||
import Decimal from "@/util/bignum";
|
import Decimal from "@/util/bignum";
|
||||||
import {
|
import {
|
||||||
|
@ -18,6 +11,7 @@ import {
|
||||||
import { createLazyProxy } from "@/util/proxies";
|
import { createLazyProxy } from "@/util/proxies";
|
||||||
import { createNanoEvents, Emitter } from "nanoevents";
|
import { createNanoEvents, Emitter } from "nanoevents";
|
||||||
import { globalBus } from "./events";
|
import { globalBus } from "./events";
|
||||||
|
import { persistent, PersistentRef } from "./persistence";
|
||||||
import player from "./player";
|
import player from "./player";
|
||||||
|
|
||||||
export interface LayerEvents {
|
export interface LayerEvents {
|
||||||
|
|
107
src/game/persistence.ts
Normal file
107
src/game/persistence.ts
Normal file
|
@ -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);
|
||||||
|
});
|
Loading…
Reference in a new issue