Persistence rework
- Removed makePersistent - Removed Persistent, renamed PersistentRef to Persistent - createLazyProxy now takes optional base object - For use where previously makePersistent would be used - Added warnings when creating refs outside a layer - Added warnings when persistent refs aren't included in their layer object - createLayer now takes id as a parameter rather than an option
This commit is contained in:
parent
bd084e51c5
commit
5a59aaf4fc
13 changed files with 296 additions and 214 deletions
|
@ -10,7 +10,7 @@ import {
|
|||
Visibility
|
||||
} from "features/feature";
|
||||
import "game/notifications";
|
||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -69,9 +69,10 @@ export type GenericAchievement = Replace<
|
|||
export function createAchievement<T extends AchievementOptions>(
|
||||
optionsFunc: () => T & ThisType<Achievement<T>>
|
||||
): Achievement<T> {
|
||||
return createLazyProxy(() => {
|
||||
const achievement: T & Partial<BaseAchievement> = optionsFunc();
|
||||
makePersistent<boolean>(achievement, false);
|
||||
return createLazyProxy(persistent => {
|
||||
// Create temp literally just to avoid explicitly assigning types
|
||||
const temp = Object.assign(persistent, optionsFunc());
|
||||
const achievement: Partial<BaseAchievement> & typeof temp = temp;
|
||||
achievement.id = getUniqueID("achievement-");
|
||||
achievement.type = AchievementType;
|
||||
achievement[Component] = AchievementComponent;
|
||||
|
@ -122,5 +123,5 @@ export function createAchievement<T extends AchievementOptions>(
|
|||
}
|
||||
|
||||
return achievement as unknown as Achievement<T>;
|
||||
});
|
||||
}, persistent<boolean>(false));
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
Visibility
|
||||
} from "features/feature";
|
||||
import { globalBus } from "game/events";
|
||||
import { State, Persistent, makePersistent, PersistentState } from "game/persistence";
|
||||
import { State, Persistent, PersistentState, persistent } from "game/persistence";
|
||||
import { isFunction } from "util/common";
|
||||
import {
|
||||
Computable,
|
||||
|
@ -199,135 +199,142 @@ export type GenericBoard = Replace<
|
|||
export function createBoard<T extends BoardOptions>(
|
||||
optionsFunc: () => T & ThisType<Board<T>>
|
||||
): Board<T> {
|
||||
return createLazyProxy(() => {
|
||||
const board: T & Partial<BaseBoard> = optionsFunc();
|
||||
makePersistent<BoardData>(board, {
|
||||
return createLazyProxy(
|
||||
persistent => {
|
||||
// Create temp literally just to avoid explicitly assigning types
|
||||
const temp = Object.assign(persistent, optionsFunc());
|
||||
const board: Partial<BaseBoard> & typeof temp = temp;
|
||||
board.id = getUniqueID("board-");
|
||||
board.type = BoardType;
|
||||
board[Component] = BoardComponent;
|
||||
|
||||
board.nodes = computed(() => processedBoard[PersistentState].value.nodes);
|
||||
board.selectedNode = computed(
|
||||
() =>
|
||||
processedBoard.nodes.value.find(
|
||||
node => node.id === board[PersistentState].value.selectedNode
|
||||
) || null
|
||||
);
|
||||
board.selectedAction = computed(() => {
|
||||
const selectedNode = processedBoard.selectedNode.value;
|
||||
if (selectedNode == null) {
|
||||
return null;
|
||||
}
|
||||
const type = processedBoard.types[selectedNode.type];
|
||||
if (type.actions == null) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
type.actions.find(
|
||||
action => action.id === processedBoard[PersistentState].value.selectedAction
|
||||
) || null
|
||||
);
|
||||
});
|
||||
board.links = computed(() => {
|
||||
if (processedBoard.selectedAction.value == null) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
processedBoard.selectedAction.value.links &&
|
||||
processedBoard.selectedNode.value
|
||||
) {
|
||||
return getNodeProperty(
|
||||
processedBoard.selectedAction.value.links,
|
||||
processedBoard.selectedNode.value
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
processComputable(board as T, "visibility");
|
||||
setDefault(board, "visibility", Visibility.Visible);
|
||||
processComputable(board as T, "width");
|
||||
setDefault(board, "width", "100%");
|
||||
processComputable(board as T, "height");
|
||||
setDefault(board, "height", "400px");
|
||||
processComputable(board as T, "classes");
|
||||
processComputable(board as T, "style");
|
||||
|
||||
for (const type in board.types) {
|
||||
const nodeType: NodeTypeOptions & Partial<BaseNodeType> = board.types[type];
|
||||
|
||||
processComputable(nodeType as NodeTypeOptions, "title");
|
||||
processComputable(nodeType as NodeTypeOptions, "label");
|
||||
processComputable(nodeType as NodeTypeOptions, "size");
|
||||
setDefault(nodeType, "size", 50);
|
||||
processComputable(nodeType as NodeTypeOptions, "draggable");
|
||||
setDefault(nodeType, "draggable", false);
|
||||
processComputable(nodeType as NodeTypeOptions, "shape");
|
||||
setDefault(nodeType, "shape", Shape.Circle);
|
||||
processComputable(nodeType as NodeTypeOptions, "canAccept");
|
||||
setDefault(nodeType, "canAccept", false);
|
||||
processComputable(nodeType as NodeTypeOptions, "progress");
|
||||
processComputable(nodeType as NodeTypeOptions, "progressDisplay");
|
||||
setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill);
|
||||
processComputable(nodeType as NodeTypeOptions, "progressColor");
|
||||
setDefault(nodeType, "progressColor", "none");
|
||||
processComputable(nodeType as NodeTypeOptions, "fillColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "outlineColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "titleColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "actionDistance");
|
||||
setDefault(nodeType, "actionDistance", Math.PI / 6);
|
||||
nodeType.nodes = computed(() =>
|
||||
board[PersistentState].value.nodes.filter(node => node.type === type)
|
||||
);
|
||||
setDefault(nodeType, "onClick", function (node: BoardNode) {
|
||||
board[PersistentState].value.selectedNode = node.id;
|
||||
});
|
||||
|
||||
if (nodeType.actions) {
|
||||
for (const action of nodeType.actions) {
|
||||
processComputable(action, "visibility");
|
||||
setDefault(action, "visibility", Visibility.Visible);
|
||||
processComputable(action, "icon");
|
||||
processComputable(action, "fillColor");
|
||||
processComputable(action, "tooltip");
|
||||
processComputable(action, "links");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
board[GatherProps] = function (this: GenericBoard) {
|
||||
const {
|
||||
nodes,
|
||||
types,
|
||||
[PersistentState]: state,
|
||||
visibility,
|
||||
width,
|
||||
height,
|
||||
style,
|
||||
classes,
|
||||
links,
|
||||
selectedAction,
|
||||
selectedNode
|
||||
} = this;
|
||||
return {
|
||||
nodes,
|
||||
types,
|
||||
[PersistentState]: state,
|
||||
visibility,
|
||||
width,
|
||||
height,
|
||||
style: unref(style),
|
||||
classes,
|
||||
links,
|
||||
selectedAction,
|
||||
selectedNode
|
||||
};
|
||||
};
|
||||
|
||||
// This is necessary because board.types is different from T and Board
|
||||
const processedBoard = board as unknown as Board<T>;
|
||||
return processedBoard;
|
||||
},
|
||||
persistent<BoardData>({
|
||||
nodes: [],
|
||||
selectedNode: null,
|
||||
selectedAction: null
|
||||
});
|
||||
board.id = getUniqueID("board-");
|
||||
board.type = BoardType;
|
||||
board[Component] = BoardComponent;
|
||||
|
||||
board.nodes = computed(() => processedBoard[PersistentState].value.nodes);
|
||||
board.selectedNode = computed(
|
||||
() =>
|
||||
processedBoard.nodes.value.find(
|
||||
node => node.id === board[PersistentState].value.selectedNode
|
||||
) || null
|
||||
);
|
||||
board.selectedAction = computed(() => {
|
||||
const selectedNode = processedBoard.selectedNode.value;
|
||||
if (selectedNode == null) {
|
||||
return null;
|
||||
}
|
||||
const type = processedBoard.types[selectedNode.type];
|
||||
if (type.actions == null) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
type.actions.find(
|
||||
action => action.id === processedBoard[PersistentState].value.selectedAction
|
||||
) || null
|
||||
);
|
||||
});
|
||||
board.links = computed(() => {
|
||||
if (processedBoard.selectedAction.value == null) {
|
||||
return null;
|
||||
}
|
||||
if (processedBoard.selectedAction.value.links && processedBoard.selectedNode.value) {
|
||||
return getNodeProperty(
|
||||
processedBoard.selectedAction.value.links,
|
||||
processedBoard.selectedNode.value
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
processComputable(board as T, "visibility");
|
||||
setDefault(board, "visibility", Visibility.Visible);
|
||||
processComputable(board as T, "width");
|
||||
setDefault(board, "width", "100%");
|
||||
processComputable(board as T, "height");
|
||||
setDefault(board, "height", "400px");
|
||||
processComputable(board as T, "classes");
|
||||
processComputable(board as T, "style");
|
||||
|
||||
for (const type in board.types) {
|
||||
const nodeType: NodeTypeOptions & Partial<BaseNodeType> = board.types[type];
|
||||
|
||||
processComputable(nodeType as NodeTypeOptions, "title");
|
||||
processComputable(nodeType as NodeTypeOptions, "label");
|
||||
processComputable(nodeType as NodeTypeOptions, "size");
|
||||
setDefault(nodeType, "size", 50);
|
||||
processComputable(nodeType as NodeTypeOptions, "draggable");
|
||||
setDefault(nodeType, "draggable", false);
|
||||
processComputable(nodeType as NodeTypeOptions, "shape");
|
||||
setDefault(nodeType, "shape", Shape.Circle);
|
||||
processComputable(nodeType as NodeTypeOptions, "canAccept");
|
||||
setDefault(nodeType, "canAccept", false);
|
||||
processComputable(nodeType as NodeTypeOptions, "progress");
|
||||
processComputable(nodeType as NodeTypeOptions, "progressDisplay");
|
||||
setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill);
|
||||
processComputable(nodeType as NodeTypeOptions, "progressColor");
|
||||
setDefault(nodeType, "progressColor", "none");
|
||||
processComputable(nodeType as NodeTypeOptions, "fillColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "outlineColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "titleColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "actionDistance");
|
||||
setDefault(nodeType, "actionDistance", Math.PI / 6);
|
||||
nodeType.nodes = computed(() =>
|
||||
board[PersistentState].value.nodes.filter(node => node.type === type)
|
||||
);
|
||||
setDefault(nodeType, "onClick", function (node: BoardNode) {
|
||||
board[PersistentState].value.selectedNode = node.id;
|
||||
});
|
||||
|
||||
if (nodeType.actions) {
|
||||
for (const action of nodeType.actions) {
|
||||
processComputable(action, "visibility");
|
||||
setDefault(action, "visibility", Visibility.Visible);
|
||||
processComputable(action, "icon");
|
||||
processComputable(action, "fillColor");
|
||||
processComputable(action, "tooltip");
|
||||
processComputable(action, "links");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
board[GatherProps] = function (this: GenericBoard) {
|
||||
const {
|
||||
nodes,
|
||||
types,
|
||||
[PersistentState]: state,
|
||||
visibility,
|
||||
width,
|
||||
height,
|
||||
style,
|
||||
classes,
|
||||
links,
|
||||
selectedAction,
|
||||
selectedNode
|
||||
} = this;
|
||||
return {
|
||||
nodes,
|
||||
types,
|
||||
[PersistentState]: state,
|
||||
visibility,
|
||||
width,
|
||||
height,
|
||||
style: unref(style),
|
||||
classes,
|
||||
links,
|
||||
selectedAction,
|
||||
selectedNode
|
||||
};
|
||||
};
|
||||
|
||||
// This is necessary because board.types is different from T and Board
|
||||
const processedBoard = board as unknown as Board<T>;
|
||||
return processedBoard;
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function getNodeProperty<T>(property: NodeComputable<T>, node: BoardNode): T {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import ClickableComponent from "features/clickables/Clickable.vue";
|
||||
import { Resource } from "features/resources/resource";
|
||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
||||
import {
|
||||
Computable,
|
||||
|
@ -89,8 +89,10 @@ export type GenericBuyable = Replace<
|
|||
export function createBuyable<T extends BuyableOptions>(
|
||||
optionsFunc: () => T & ThisType<Buyable<T>>
|
||||
): Buyable<T> {
|
||||
return createLazyProxy(() => {
|
||||
const buyable: T & Partial<BaseBuyable> = optionsFunc();
|
||||
return createLazyProxy(persistent => {
|
||||
// Create temp literally just to avoid explicitly assigning types
|
||||
const temp = Object.assign(persistent, optionsFunc());
|
||||
const buyable: Partial<BaseBuyable> & typeof temp = temp;
|
||||
|
||||
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
||||
console.warn(
|
||||
|
@ -100,7 +102,6 @@ export function createBuyable<T extends BuyableOptions>(
|
|||
throw "Cannot create buyable without a canPurchase property or a resource and cost property";
|
||||
}
|
||||
|
||||
makePersistent<DecimalSource>(buyable, 0);
|
||||
buyable.id = getUniqueID("buyable-");
|
||||
buyable.type = BuyableType;
|
||||
buyable[Component] = ClickableComponent;
|
||||
|
@ -239,5 +240,5 @@ export function createBuyable<T extends BuyableOptions>(
|
|||
};
|
||||
|
||||
return buyable as unknown as Buyable<T>;
|
||||
});
|
||||
}, persistent<DecimalSource>(0));
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
import { GenericReset } from "features/reset";
|
||||
import { Resource } from "features/resources/resource";
|
||||
import { globalBus } from "game/events";
|
||||
import { persistent, PersistentRef } from "game/persistence";
|
||||
import { Persistent, persistent } from "game/persistence";
|
||||
import settings, { registerSettingField } from "game/settings";
|
||||
import Decimal, { DecimalSource } from "util/bignum";
|
||||
import {
|
||||
|
@ -58,10 +58,10 @@ export interface ChallengeOptions {
|
|||
|
||||
export interface BaseChallenge {
|
||||
id: string;
|
||||
completions: PersistentRef<DecimalSource>;
|
||||
completions: Persistent<DecimalSource>;
|
||||
completed: Ref<boolean>;
|
||||
maxed: Ref<boolean>;
|
||||
active: PersistentRef<boolean>;
|
||||
active: Persistent<boolean>;
|
||||
toggle: VoidFunction;
|
||||
complete: (remainInChallenge?: boolean) => void;
|
||||
type: typeof ChallengeType;
|
||||
|
@ -98,6 +98,8 @@ export type GenericChallenge = Replace<
|
|||
export function createChallenge<T extends ChallengeOptions>(
|
||||
optionsFunc: () => T & ThisType<Challenge<T>>
|
||||
): Challenge<T> {
|
||||
const completions = persistent(0);
|
||||
const active = persistent(false);
|
||||
return createLazyProxy(() => {
|
||||
const challenge: T & Partial<BaseChallenge> = optionsFunc();
|
||||
|
||||
|
@ -116,8 +118,8 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
challenge.type = ChallengeType;
|
||||
challenge[Component] = ChallengeComponent;
|
||||
|
||||
challenge.completions = persistent(0);
|
||||
challenge.active = persistent(false);
|
||||
challenge.completions = completions;
|
||||
challenge.active = active;
|
||||
challenge.completed = computed(() =>
|
||||
Decimal.gt((challenge as GenericChallenge).completions.value, 0)
|
||||
);
|
||||
|
|
|
@ -19,7 +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";
|
||||
import { State, Persistent, PersistentState, persistent } from "game/persistence";
|
||||
|
||||
export const GridType = Symbol("Grid");
|
||||
|
||||
|
@ -243,9 +243,10 @@ export type GenericGrid = Replace<
|
|||
export function createGrid<T extends GridOptions>(
|
||||
optionsFunc: () => T & ThisType<Grid<T>>
|
||||
): Grid<T> {
|
||||
return createLazyProxy(() => {
|
||||
const grid: T & Partial<BaseGrid> = optionsFunc();
|
||||
makePersistent(grid, {});
|
||||
return createLazyProxy(persistent => {
|
||||
// Create temp literally just to avoid explicitly assigning types
|
||||
const temp = Object.assign(persistent, optionsFunc());
|
||||
const grid: Partial<BaseGrid> & typeof temp = temp;
|
||||
grid.id = getUniqueID("grid-");
|
||||
grid[Component] = GridComponent;
|
||||
|
||||
|
@ -301,5 +302,5 @@ export function createGrid<T extends GridOptions>(
|
|||
};
|
||||
|
||||
return grid as unknown as Grid<T>;
|
||||
});
|
||||
}, persistent({}));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Ref, unref } from "vue";
|
||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||
|
||||
export const InfoboxType = Symbol("Infobox");
|
||||
|
||||
|
@ -65,9 +65,10 @@ export type GenericInfobox = Replace<
|
|||
export function createInfobox<T extends InfoboxOptions>(
|
||||
optionsFunc: () => T & ThisType<Infobox<T>>
|
||||
): Infobox<T> {
|
||||
return createLazyProxy(() => {
|
||||
const infobox: T & Partial<BaseInfobox> = optionsFunc();
|
||||
makePersistent<boolean>(infobox, false);
|
||||
return createLazyProxy(persistent => {
|
||||
// Create temp literally just to avoid explicitly assigning types
|
||||
const temp = Object.assign(persistent, optionsFunc());
|
||||
const infobox: Partial<BaseInfobox> & typeof temp = temp;
|
||||
infobox.id = getUniqueID("infobox-");
|
||||
infobox.type = InfoboxType;
|
||||
infobox[Component] = InfoboxComponent;
|
||||
|
@ -112,5 +113,5 @@ export function createInfobox<T extends InfoboxOptions>(
|
|||
};
|
||||
|
||||
return infobox as unknown as Infobox<T>;
|
||||
});
|
||||
}, persistent<boolean>(false));
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
import MilestoneComponent from "features/milestones/Milestone.vue";
|
||||
import { globalBus } from "game/events";
|
||||
import "game/notifications";
|
||||
import { makePersistent, Persistent, PersistentState } from "game/persistence";
|
||||
import { persistent, Persistent, PersistentState } from "game/persistence";
|
||||
import settings, { registerSettingField } from "game/settings";
|
||||
import { camelToTitle } from "util/common";
|
||||
import {
|
||||
|
@ -85,9 +85,10 @@ export type GenericMilestone = Replace<
|
|||
export function createMilestone<T extends MilestoneOptions>(
|
||||
optionsFunc: () => T & ThisType<Milestone<T>>
|
||||
): Milestone<T> {
|
||||
return createLazyProxy(() => {
|
||||
const milestone: T & Partial<BaseMilestone> = optionsFunc();
|
||||
makePersistent<boolean>(milestone, false);
|
||||
return createLazyProxy(persistent => {
|
||||
// Create temp literally just to avoid explicitly assigning types
|
||||
const temp = Object.assign(persistent, optionsFunc());
|
||||
const milestone: Partial<BaseMilestone> & typeof temp = temp;
|
||||
milestone.id = getUniqueID("milestone-");
|
||||
milestone.type = MilestoneType;
|
||||
milestone[Component] = MilestoneComponent;
|
||||
|
@ -168,7 +169,7 @@ export function createMilestone<T extends MilestoneOptions>(
|
|||
}
|
||||
|
||||
return milestone as unknown as Milestone<T>;
|
||||
});
|
||||
}, persistent<boolean>(false));
|
||||
}
|
||||
|
||||
declare module "game/settings" {
|
||||
|
|
|
@ -10,7 +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 { Persistent, PersistentState, persistent } from "game/persistence";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -60,13 +60,13 @@ export type GenericTabButton = Replace<
|
|||
|
||||
export interface TabFamilyOptions {
|
||||
visibility?: Computable<Visibility>;
|
||||
tabs: Record<string, TabButtonOptions>;
|
||||
classes?: Computable<Record<string, boolean>>;
|
||||
style?: Computable<StyleValue>;
|
||||
}
|
||||
|
||||
export interface BaseTabFamily extends Persistent<string> {
|
||||
id: string;
|
||||
tabs: Record<string, TabButtonOptions>;
|
||||
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
||||
selected: Ref<string>;
|
||||
type: typeof TabFamilyType;
|
||||
|
@ -90,21 +90,41 @@ export type GenericTabFamily = Replace<
|
|||
>;
|
||||
|
||||
export function createTabFamily<T extends TabFamilyOptions>(
|
||||
tabs: Record<string, () => TabButtonOptions>,
|
||||
optionsFunc: () => T & ThisType<TabFamily<T>>
|
||||
): TabFamily<T> {
|
||||
return createLazyProxy(() => {
|
||||
const tabFamily: T & Partial<BaseTabFamily> = optionsFunc();
|
||||
if (Object.keys(tabs).length === 0) {
|
||||
console.warn("Cannot create tab family with 0 tabs");
|
||||
throw "Cannot create tab family with 0 tabs";
|
||||
}
|
||||
|
||||
if (Object.keys(tabFamily.tabs).length === 0) {
|
||||
console.warn("Cannot create tab family with 0 tabs", tabFamily);
|
||||
throw "Cannot create tab family with 0 tabs";
|
||||
}
|
||||
return createLazyProxy(persistent => {
|
||||
// Create temp literally just to avoid explicitly assigning types
|
||||
const temp = Object.assign(persistent, optionsFunc());
|
||||
const tabFamily: Partial<BaseTabFamily> & typeof temp = temp;
|
||||
|
||||
tabFamily.id = getUniqueID("tabFamily-");
|
||||
tabFamily.type = TabFamilyType;
|
||||
tabFamily[Component] = TabFamilyComponent;
|
||||
|
||||
makePersistent<string>(tabFamily, Object.keys(tabFamily.tabs)[0]);
|
||||
tabFamily.tabs = Object.keys(tabs).reduce<Record<string, GenericTabButton>>(
|
||||
(parsedTabs, tab) => {
|
||||
const tabButton: TabButtonOptions & Partial<BaseTabButton> = tabs[tab]();
|
||||
tabButton.type = TabButtonType;
|
||||
tabButton[Component] = TabButtonComponent;
|
||||
|
||||
processComputable(tabButton as TabButtonOptions, "visibility");
|
||||
setDefault(tabButton, "visibility", Visibility.Visible);
|
||||
processComputable(tabButton as TabButtonOptions, "tab");
|
||||
processComputable(tabButton as TabButtonOptions, "display");
|
||||
processComputable(tabButton as TabButtonOptions, "classes");
|
||||
processComputable(tabButton as TabButtonOptions, "style");
|
||||
processComputable(tabButton as TabButtonOptions, "glowColor");
|
||||
parsedTabs[tab] = tabButton as GenericTabButton;
|
||||
return parsedTabs;
|
||||
},
|
||||
{}
|
||||
);
|
||||
tabFamily.selected = tabFamily[PersistentState];
|
||||
tabFamily.activeTab = computed(() => {
|
||||
const tabs = unref(processedTabFamily.tabs);
|
||||
|
@ -129,20 +149,6 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
|||
processComputable(tabFamily as T, "classes");
|
||||
processComputable(tabFamily as T, "style");
|
||||
|
||||
for (const tab in tabFamily.tabs) {
|
||||
const tabButton: TabButtonOptions & Partial<BaseTabButton> = tabFamily.tabs[tab];
|
||||
tabButton.type = TabButtonType;
|
||||
tabButton[Component] = TabButtonComponent;
|
||||
|
||||
processComputable(tabButton as TabButtonOptions, "visibility");
|
||||
setDefault(tabButton, "visibility", Visibility.Visible);
|
||||
processComputable(tabButton as TabButtonOptions, "tab");
|
||||
processComputable(tabButton as TabButtonOptions, "display");
|
||||
processComputable(tabButton as TabButtonOptions, "classes");
|
||||
processComputable(tabButton as TabButtonOptions, "style");
|
||||
processComputable(tabButton as TabButtonOptions, "glowColor");
|
||||
}
|
||||
|
||||
tabFamily[GatherProps] = function (this: GenericTabFamily) {
|
||||
const { visibility, activeTab, selected, tabs, style, classes } = this;
|
||||
return { visibility, activeTab, selected, tabs, style: unref(style), classes };
|
||||
|
@ -151,5 +157,5 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
|||
// This is necessary because board.types is different from T and TabFamily
|
||||
const processedTabFamily = tabFamily as unknown as TabFamily<T>;
|
||||
return processedTabFamily;
|
||||
});
|
||||
}, persistent(Object.keys(tabs)[0]));
|
||||
}
|
||||
|
|
|
@ -13,7 +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 { deletePersistent, persistent } from "game/persistence";
|
||||
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
||||
import {
|
||||
Computable,
|
||||
|
@ -76,16 +76,18 @@ export type GenericTreeNode = Replace<
|
|||
export function createTreeNode<T extends TreeNodeOptions>(
|
||||
optionsFunc: () => T & ThisType<TreeNode<T>>
|
||||
): TreeNode<T> {
|
||||
const forceTooltip = persistent(false);
|
||||
return createLazyProxy(() => {
|
||||
const treeNode: T & Partial<BaseTreeNode> = optionsFunc();
|
||||
treeNode.id = getUniqueID("treeNode-");
|
||||
treeNode.type = TreeNodeType;
|
||||
|
||||
if (treeNode.tooltip) {
|
||||
treeNode.forceTooltip = persistent(false);
|
||||
treeNode.forceTooltip = forceTooltip;
|
||||
} else {
|
||||
// If we don't have a tooltip, no point in making this persistent
|
||||
treeNode.forceTooltip = ref(false);
|
||||
deletePersistent(forceTooltip);
|
||||
}
|
||||
|
||||
processComputable(treeNode as T, "visibility");
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
||||
import { persistent, Persistent, PersistentState } from "game/persistence";
|
||||
|
||||
export const UpgradeType = Symbol("Upgrade");
|
||||
|
||||
|
@ -80,9 +80,10 @@ export type GenericUpgrade = Replace<
|
|||
export function createUpgrade<T extends UpgradeOptions>(
|
||||
optionsFunc: () => T & ThisType<Upgrade<T>>
|
||||
): Upgrade<T> {
|
||||
return createLazyProxy(() => {
|
||||
const upgrade: T & Partial<BaseUpgrade> = optionsFunc();
|
||||
makePersistent<boolean>(upgrade, false);
|
||||
return createLazyProxy(persistent => {
|
||||
// Create temp literally just to avoid explicitly assigning types
|
||||
const temp = Object.assign(persistent, optionsFunc());
|
||||
const upgrade: Partial<BaseUpgrade> & typeof temp = temp;
|
||||
upgrade.id = getUniqueID("upgrade-");
|
||||
upgrade.type = UpgradeType;
|
||||
upgrade[Component] = UpgradeComponent;
|
||||
|
@ -167,7 +168,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
|||
};
|
||||
|
||||
return upgrade as unknown as Upgrade<T>;
|
||||
});
|
||||
}, persistent<boolean>(false));
|
||||
}
|
||||
|
||||
export function setupAutoPurchase(
|
||||
|
|
|
@ -18,7 +18,7 @@ import { createLazyProxy } from "util/proxies";
|
|||
import { createNanoEvents, Emitter } from "nanoevents";
|
||||
import { InjectionKey, Ref, ref, unref } from "vue";
|
||||
import { globalBus } from "./events";
|
||||
import { persistent, PersistentRef } from "./persistence";
|
||||
import { Persistent, persistent } from "./persistence";
|
||||
import player from "./player";
|
||||
|
||||
export interface FeatureNode {
|
||||
|
@ -58,7 +58,6 @@ export interface Position {
|
|||
}
|
||||
|
||||
export interface LayerOptions {
|
||||
id: string;
|
||||
color?: Computable<string>;
|
||||
display: Computable<CoercableComponent>;
|
||||
classes?: Computable<Record<string, boolean>>;
|
||||
|
@ -70,7 +69,8 @@ export interface LayerOptions {
|
|||
}
|
||||
|
||||
export interface BaseLayer {
|
||||
minimized: PersistentRef<boolean>;
|
||||
id: string;
|
||||
minimized: Persistent<boolean>;
|
||||
emitter: Emitter<LayerEvents>;
|
||||
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
||||
emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
|
||||
|
@ -84,7 +84,7 @@ export type Layer<T extends LayerOptions> = Replace<
|
|||
display: GetComputableType<T["display"]>;
|
||||
classes: GetComputableType<T["classes"]>;
|
||||
style: GetComputableType<T["style"]>;
|
||||
name: GetComputableTypeWithDefault<T["name"], T["id"]>;
|
||||
name: GetComputableTypeWithDefault<T["name"], string>;
|
||||
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
|
||||
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
|
||||
forceHideGoBack: GetComputableType<T["forceHideGoBack"]>;
|
||||
|
@ -100,7 +100,10 @@ export type GenericLayer = Replace<
|
|||
}
|
||||
>;
|
||||
|
||||
export const persistentRefs: Record<string, Set<Persistent>> = {};
|
||||
export const addingLayers: string[] = [];
|
||||
export function createLayer<T extends LayerOptions>(
|
||||
id: string,
|
||||
optionsFunc: (() => T) & ThisType<BaseLayer>
|
||||
): Layer<T> {
|
||||
return createLazyProxy(() => {
|
||||
|
@ -109,10 +112,19 @@ export function createLayer<T extends LayerOptions>(
|
|||
layer.on = emitter.on.bind(emitter);
|
||||
layer.emit = emitter.emit.bind(emitter);
|
||||
layer.nodes = ref({});
|
||||
layer.id = id;
|
||||
|
||||
addingLayers.push(id);
|
||||
persistentRefs[id] = new Set();
|
||||
layer.minimized = persistent(false);
|
||||
|
||||
Object.assign(layer, optionsFunc.call(layer));
|
||||
if (
|
||||
addingLayers[addingLayers.length - 1] == null ||
|
||||
addingLayers[addingLayers.length - 1] !== id
|
||||
) {
|
||||
throw `Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}`;
|
||||
}
|
||||
addingLayers.pop();
|
||||
|
||||
processComputable(layer as T, "color");
|
||||
processComputable(layer as T, "display");
|
||||
|
|
|
@ -3,10 +3,12 @@ import Decimal, { DecimalSource } from "util/bignum";
|
|||
import { ProxyState } from "util/proxies";
|
||||
import { isArray } from "@vue/shared";
|
||||
import { isReactive, isRef, Ref, ref } from "vue";
|
||||
import { GenericLayer } from "./layers";
|
||||
import { addingLayers, GenericLayer, persistentRefs } from "./layers";
|
||||
|
||||
export const PersistentState = Symbol("PersistentState");
|
||||
export const DefaultValue = Symbol("DefaultValue");
|
||||
export const StackTrace = Symbol("StackTrace");
|
||||
export const Deleted = Symbol("Deleted");
|
||||
|
||||
// Note: This is a union of things that should be safely stringifiable without needing
|
||||
// special processes for knowing what to load them in as
|
||||
|
@ -20,31 +22,52 @@ export type State =
|
|||
| { [key: string]: State }
|
||||
| { [key: number]: State };
|
||||
|
||||
export type Persistent<T extends State = State> = {
|
||||
export type Persistent<T extends State = State> = Ref<T> & {
|
||||
[PersistentState]: Ref<T>;
|
||||
[DefaultValue]: T;
|
||||
[StackTrace]: string;
|
||||
[Deleted]: boolean;
|
||||
};
|
||||
export type PersistentRef<T extends State = State> = Ref<T> & Persistent<T>;
|
||||
|
||||
export function persistent<T extends State>(defaultValue: T | Ref<T>): PersistentRef<T> {
|
||||
function getStackTrace() {
|
||||
return (
|
||||
new Error().stack
|
||||
?.split("\n")
|
||||
.slice(3, 5)
|
||||
.map(line => line.trim())
|
||||
.join("\n") || ""
|
||||
);
|
||||
}
|
||||
|
||||
export function persistent<T extends State>(defaultValue: T | Ref<T>): Persistent<T> {
|
||||
const persistent = (
|
||||
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
|
||||
) as PersistentRef<T>;
|
||||
) as Persistent<T>;
|
||||
|
||||
persistent[PersistentState] = persistent;
|
||||
persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
|
||||
return persistent as PersistentRef<T>;
|
||||
persistent[StackTrace] = getStackTrace();
|
||||
persistent[Deleted] = false;
|
||||
|
||||
if (addingLayers.length === 0) {
|
||||
console.warn(
|
||||
"Creating a persistent ref outside of a layer. This is not officially supported",
|
||||
persistent,
|
||||
"\nCreated at:\n" + persistent[StackTrace]
|
||||
);
|
||||
} else {
|
||||
persistentRefs[addingLayers[addingLayers.length - 1]].add(persistent);
|
||||
}
|
||||
|
||||
return persistent as Persistent<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 deletePersistent(persistent: Persistent) {
|
||||
if (addingLayers.length === 0) {
|
||||
console.warn("Deleting a persistent ref outside of a layer. Ignoring...", persistent);
|
||||
}
|
||||
persistentRefs[addingLayers[addingLayers.length - 1]].delete(persistent);
|
||||
persistent[Deleted] = true;
|
||||
}
|
||||
|
||||
globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => {
|
||||
|
@ -56,6 +79,19 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
|||
if (value && typeof value === "object") {
|
||||
if (PersistentState in value) {
|
||||
foundPersistent = true;
|
||||
if ((value as Persistent)[Deleted]) {
|
||||
console.warn(
|
||||
"Deleted persistent ref present in returned object. Ignoring...",
|
||||
value,
|
||||
"\nCreated at:\n" + (value as Persistent)[StackTrace]
|
||||
);
|
||||
return;
|
||||
}
|
||||
persistentRefs[layer.id].delete(
|
||||
ProxyState in value
|
||||
? ((value as any)[ProxyState] as Persistent)
|
||||
: (value as Persistent)
|
||||
);
|
||||
|
||||
// Construct save path if it doesn't exist
|
||||
const persistentState = path.reduce<Record<string, unknown>>((acc, curr) => {
|
||||
|
@ -122,4 +158,12 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
|||
return foundPersistent;
|
||||
};
|
||||
handleObject(layer);
|
||||
persistentRefs[layer.id].forEach(persistent => {
|
||||
console.error(
|
||||
`Created persistent ref in ${layer.id} without registering it to the layer! Make sure to include everything persistent in the returned object`,
|
||||
persistent,
|
||||
"\nCreated at:\n" + persistent[StackTrace]
|
||||
);
|
||||
});
|
||||
persistentRefs[layer.id].clear();
|
||||
});
|
||||
|
|
|
@ -17,15 +17,18 @@ export type ProxiedWithState<T> = NonNullable<T> extends Record<PropertyKey, any
|
|||
|
||||
// Takes a function that returns an object and pretends to be that object
|
||||
// Note that the object is lazily calculated
|
||||
export function createLazyProxy<T extends object>(objectFunc: () => T): T {
|
||||
const obj: T | Record<string, never> = {};
|
||||
export function createLazyProxy<T extends object, S>(
|
||||
objectFunc: (baseObject: S) => T & S,
|
||||
baseObject: S = {} as S
|
||||
): T {
|
||||
const obj: S & Partial<T> = baseObject;
|
||||
let calculated = false;
|
||||
function calculateObj(): T {
|
||||
if (!calculated) {
|
||||
Object.assign(obj, objectFunc());
|
||||
Object.assign(obj, objectFunc(obj));
|
||||
calculated = true;
|
||||
}
|
||||
return obj as T;
|
||||
return obj as S & T;
|
||||
}
|
||||
|
||||
return new Proxy(obj, {
|
||||
|
@ -51,10 +54,10 @@ export function createLazyProxy<T extends object>(objectFunc: () => T): T {
|
|||
},
|
||||
getOwnPropertyDescriptor(target, key) {
|
||||
if (!calculated) {
|
||||
Object.assign(obj, objectFunc());
|
||||
Object.assign(obj, objectFunc(obj));
|
||||
calculated = true;
|
||||
}
|
||||
return Object.getOwnPropertyDescriptor(target, key);
|
||||
}
|
||||
}) as T;
|
||||
}) as S & T;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue