forked from profectus/Profectus
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,
|
||||
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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
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