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
|
Visibility
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -69,9 +69,10 @@ export type GenericAchievement = Replace<
|
||||||
export function createAchievement<T extends AchievementOptions>(
|
export function createAchievement<T extends AchievementOptions>(
|
||||||
optionsFunc: () => T & ThisType<Achievement<T>>
|
optionsFunc: () => T & ThisType<Achievement<T>>
|
||||||
): Achievement<T> {
|
): Achievement<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const achievement: T & Partial<BaseAchievement> = optionsFunc();
|
// Create temp literally just to avoid explicitly assigning types
|
||||||
makePersistent<boolean>(achievement, false);
|
const temp = Object.assign(persistent, optionsFunc());
|
||||||
|
const achievement: Partial<BaseAchievement> & typeof temp = temp;
|
||||||
achievement.id = getUniqueID("achievement-");
|
achievement.id = getUniqueID("achievement-");
|
||||||
achievement.type = AchievementType;
|
achievement.type = AchievementType;
|
||||||
achievement[Component] = AchievementComponent;
|
achievement[Component] = AchievementComponent;
|
||||||
|
@ -122,5 +123,5 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
return achievement as unknown as Achievement<T>;
|
return achievement as unknown as Achievement<T>;
|
||||||
});
|
}, persistent<boolean>(false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
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 { State, Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
import { isFunction } from "util/common";
|
import { isFunction } from "util/common";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -199,135 +199,142 @@ export type GenericBoard = Replace<
|
||||||
export function createBoard<T extends BoardOptions>(
|
export function createBoard<T extends BoardOptions>(
|
||||||
optionsFunc: () => T & ThisType<Board<T>>
|
optionsFunc: () => T & ThisType<Board<T>>
|
||||||
): Board<T> {
|
): Board<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(
|
||||||
const board: T & Partial<BaseBoard> = optionsFunc();
|
persistent => {
|
||||||
makePersistent<BoardData>(board, {
|
// 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: [],
|
nodes: [],
|
||||||
selectedNode: null,
|
selectedNode: null,
|
||||||
selectedAction: 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 {
|
export function getNodeProperty<T>(property: NodeComputable<T>, node: BoardNode): T {
|
||||||
|
|
|
@ -1,6 +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 { Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -89,8 +89,10 @@ export type GenericBuyable = Replace<
|
||||||
export function createBuyable<T extends BuyableOptions>(
|
export function createBuyable<T extends BuyableOptions>(
|
||||||
optionsFunc: () => T & ThisType<Buyable<T>>
|
optionsFunc: () => T & ThisType<Buyable<T>>
|
||||||
): Buyable<T> {
|
): Buyable<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const buyable: T & Partial<BaseBuyable> = optionsFunc();
|
// 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)) {
|
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
||||||
console.warn(
|
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";
|
throw "Cannot create buyable without a canPurchase property or a resource and cost property";
|
||||||
}
|
}
|
||||||
|
|
||||||
makePersistent<DecimalSource>(buyable, 0);
|
|
||||||
buyable.id = getUniqueID("buyable-");
|
buyable.id = getUniqueID("buyable-");
|
||||||
buyable.type = BuyableType;
|
buyable.type = BuyableType;
|
||||||
buyable[Component] = ClickableComponent;
|
buyable[Component] = ClickableComponent;
|
||||||
|
@ -239,5 +240,5 @@ export function createBuyable<T extends BuyableOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return buyable as unknown as Buyable<T>;
|
return buyable as unknown as Buyable<T>;
|
||||||
});
|
}, persistent<DecimalSource>(0));
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,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 { persistent, PersistentRef } from "game/persistence";
|
import { Persistent, persistent } from "game/persistence";
|
||||||
import settings, { registerSettingField } from "game/settings";
|
import settings, { registerSettingField } from "game/settings";
|
||||||
import Decimal, { DecimalSource } from "util/bignum";
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import {
|
import {
|
||||||
|
@ -58,10 +58,10 @@ export interface ChallengeOptions {
|
||||||
|
|
||||||
export interface BaseChallenge {
|
export interface BaseChallenge {
|
||||||
id: string;
|
id: string;
|
||||||
completions: PersistentRef<DecimalSource>;
|
completions: Persistent<DecimalSource>;
|
||||||
completed: Ref<boolean>;
|
completed: Ref<boolean>;
|
||||||
maxed: Ref<boolean>;
|
maxed: Ref<boolean>;
|
||||||
active: PersistentRef<boolean>;
|
active: Persistent<boolean>;
|
||||||
toggle: VoidFunction;
|
toggle: VoidFunction;
|
||||||
complete: (remainInChallenge?: boolean) => void;
|
complete: (remainInChallenge?: boolean) => void;
|
||||||
type: typeof ChallengeType;
|
type: typeof ChallengeType;
|
||||||
|
@ -98,6 +98,8 @@ export type GenericChallenge = Replace<
|
||||||
export function createChallenge<T extends ChallengeOptions>(
|
export function createChallenge<T extends ChallengeOptions>(
|
||||||
optionsFunc: () => T & ThisType<Challenge<T>>
|
optionsFunc: () => T & ThisType<Challenge<T>>
|
||||||
): Challenge<T> {
|
): Challenge<T> {
|
||||||
|
const completions = persistent(0);
|
||||||
|
const active = persistent(false);
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const challenge: T & Partial<BaseChallenge> = optionsFunc();
|
const challenge: T & Partial<BaseChallenge> = optionsFunc();
|
||||||
|
|
||||||
|
@ -116,8 +118,8 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
challenge.type = ChallengeType;
|
challenge.type = ChallengeType;
|
||||||
challenge[Component] = ChallengeComponent;
|
challenge[Component] = ChallengeComponent;
|
||||||
|
|
||||||
challenge.completions = persistent(0);
|
challenge.completions = completions;
|
||||||
challenge.active = persistent(false);
|
challenge.active = active;
|
||||||
challenge.completed = computed(() =>
|
challenge.completed = computed(() =>
|
||||||
Decimal.gt((challenge as GenericChallenge).completions.value, 0)
|
Decimal.gt((challenge as GenericChallenge).completions.value, 0)
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,7 +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";
|
import { State, Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
|
|
||||||
export const GridType = Symbol("Grid");
|
export const GridType = Symbol("Grid");
|
||||||
|
|
||||||
|
@ -243,9 +243,10 @@ export type GenericGrid = Replace<
|
||||||
export function createGrid<T extends GridOptions>(
|
export function createGrid<T extends GridOptions>(
|
||||||
optionsFunc: () => T & ThisType<Grid<T>>
|
optionsFunc: () => T & ThisType<Grid<T>>
|
||||||
): Grid<T> {
|
): Grid<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const grid: T & Partial<BaseGrid> = optionsFunc();
|
// Create temp literally just to avoid explicitly assigning types
|
||||||
makePersistent(grid, {});
|
const temp = Object.assign(persistent, optionsFunc());
|
||||||
|
const grid: Partial<BaseGrid> & typeof temp = temp;
|
||||||
grid.id = getUniqueID("grid-");
|
grid.id = getUniqueID("grid-");
|
||||||
grid[Component] = GridComponent;
|
grid[Component] = GridComponent;
|
||||||
|
|
||||||
|
@ -301,5 +302,5 @@ export function createGrid<T extends GridOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return grid as unknown as Grid<T>;
|
return grid as unknown as Grid<T>;
|
||||||
});
|
}, persistent({}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { Ref, unref } from "vue";
|
import { Ref, unref } from "vue";
|
||||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
|
|
||||||
export const InfoboxType = Symbol("Infobox");
|
export const InfoboxType = Symbol("Infobox");
|
||||||
|
|
||||||
|
@ -65,9 +65,10 @@ export type GenericInfobox = Replace<
|
||||||
export function createInfobox<T extends InfoboxOptions>(
|
export function createInfobox<T extends InfoboxOptions>(
|
||||||
optionsFunc: () => T & ThisType<Infobox<T>>
|
optionsFunc: () => T & ThisType<Infobox<T>>
|
||||||
): Infobox<T> {
|
): Infobox<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const infobox: T & Partial<BaseInfobox> = optionsFunc();
|
// Create temp literally just to avoid explicitly assigning types
|
||||||
makePersistent<boolean>(infobox, false);
|
const temp = Object.assign(persistent, optionsFunc());
|
||||||
|
const infobox: Partial<BaseInfobox> & typeof temp = temp;
|
||||||
infobox.id = getUniqueID("infobox-");
|
infobox.id = getUniqueID("infobox-");
|
||||||
infobox.type = InfoboxType;
|
infobox.type = InfoboxType;
|
||||||
infobox[Component] = InfoboxComponent;
|
infobox[Component] = InfoboxComponent;
|
||||||
|
@ -112,5 +113,5 @@ export function createInfobox<T extends InfoboxOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return infobox as unknown as Infobox<T>;
|
return infobox as unknown as Infobox<T>;
|
||||||
});
|
}, persistent<boolean>(false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
import MilestoneComponent from "features/milestones/Milestone.vue";
|
import MilestoneComponent from "features/milestones/Milestone.vue";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import { makePersistent, Persistent, PersistentState } from "game/persistence";
|
import { persistent, Persistent, PersistentState } from "game/persistence";
|
||||||
import settings, { registerSettingField } from "game/settings";
|
import settings, { registerSettingField } from "game/settings";
|
||||||
import { camelToTitle } from "util/common";
|
import { camelToTitle } from "util/common";
|
||||||
import {
|
import {
|
||||||
|
@ -85,9 +85,10 @@ export type GenericMilestone = Replace<
|
||||||
export function createMilestone<T extends MilestoneOptions>(
|
export function createMilestone<T extends MilestoneOptions>(
|
||||||
optionsFunc: () => T & ThisType<Milestone<T>>
|
optionsFunc: () => T & ThisType<Milestone<T>>
|
||||||
): Milestone<T> {
|
): Milestone<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const milestone: T & Partial<BaseMilestone> = optionsFunc();
|
// Create temp literally just to avoid explicitly assigning types
|
||||||
makePersistent<boolean>(milestone, false);
|
const temp = Object.assign(persistent, optionsFunc());
|
||||||
|
const milestone: Partial<BaseMilestone> & typeof temp = temp;
|
||||||
milestone.id = getUniqueID("milestone-");
|
milestone.id = getUniqueID("milestone-");
|
||||||
milestone.type = MilestoneType;
|
milestone.type = MilestoneType;
|
||||||
milestone[Component] = MilestoneComponent;
|
milestone[Component] = MilestoneComponent;
|
||||||
|
@ -168,7 +169,7 @@ export function createMilestone<T extends MilestoneOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
return milestone as unknown as Milestone<T>;
|
return milestone as unknown as Milestone<T>;
|
||||||
});
|
}, persistent<boolean>(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "game/settings" {
|
declare module "game/settings" {
|
||||||
|
|
|
@ -10,7 +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 { Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -60,13 +60,13 @@ export type GenericTabButton = Replace<
|
||||||
|
|
||||||
export interface TabFamilyOptions {
|
export interface TabFamilyOptions {
|
||||||
visibility?: Computable<Visibility>;
|
visibility?: Computable<Visibility>;
|
||||||
tabs: Record<string, TabButtonOptions>;
|
|
||||||
classes?: Computable<Record<string, boolean>>;
|
classes?: Computable<Record<string, boolean>>;
|
||||||
style?: Computable<StyleValue>;
|
style?: Computable<StyleValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseTabFamily extends Persistent<string> {
|
export interface BaseTabFamily extends Persistent<string> {
|
||||||
id: string;
|
id: string;
|
||||||
|
tabs: Record<string, TabButtonOptions>;
|
||||||
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
||||||
selected: Ref<string>;
|
selected: Ref<string>;
|
||||||
type: typeof TabFamilyType;
|
type: typeof TabFamilyType;
|
||||||
|
@ -90,21 +90,41 @@ export type GenericTabFamily = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createTabFamily<T extends TabFamilyOptions>(
|
export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
|
tabs: Record<string, () => TabButtonOptions>,
|
||||||
optionsFunc: () => T & ThisType<TabFamily<T>>
|
optionsFunc: () => T & ThisType<TabFamily<T>>
|
||||||
): TabFamily<T> {
|
): TabFamily<T> {
|
||||||
return createLazyProxy(() => {
|
if (Object.keys(tabs).length === 0) {
|
||||||
const tabFamily: T & Partial<BaseTabFamily> = optionsFunc();
|
console.warn("Cannot create tab family with 0 tabs");
|
||||||
|
throw "Cannot create tab family with 0 tabs";
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(tabFamily.tabs).length === 0) {
|
return createLazyProxy(persistent => {
|
||||||
console.warn("Cannot create tab family with 0 tabs", tabFamily);
|
// Create temp literally just to avoid explicitly assigning types
|
||||||
throw "Cannot create tab family with 0 tabs";
|
const temp = Object.assign(persistent, optionsFunc());
|
||||||
}
|
const tabFamily: Partial<BaseTabFamily> & typeof temp = temp;
|
||||||
|
|
||||||
tabFamily.id = getUniqueID("tabFamily-");
|
tabFamily.id = getUniqueID("tabFamily-");
|
||||||
tabFamily.type = TabFamilyType;
|
tabFamily.type = TabFamilyType;
|
||||||
tabFamily[Component] = TabFamilyComponent;
|
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.selected = tabFamily[PersistentState];
|
||||||
tabFamily.activeTab = computed(() => {
|
tabFamily.activeTab = computed(() => {
|
||||||
const tabs = unref(processedTabFamily.tabs);
|
const tabs = unref(processedTabFamily.tabs);
|
||||||
|
@ -129,20 +149,6 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
processComputable(tabFamily as T, "classes");
|
processComputable(tabFamily as T, "classes");
|
||||||
processComputable(tabFamily as T, "style");
|
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) {
|
tabFamily[GatherProps] = function (this: GenericTabFamily) {
|
||||||
const { visibility, activeTab, selected, tabs, style, classes } = this;
|
const { visibility, activeTab, selected, tabs, style, classes } = this;
|
||||||
return { visibility, activeTab, selected, tabs, style: unref(style), classes };
|
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
|
// This is necessary because board.types is different from T and TabFamily
|
||||||
const processedTabFamily = tabFamily as unknown as TabFamily<T>;
|
const processedTabFamily = tabFamily as unknown as TabFamily<T>;
|
||||||
return processedTabFamily;
|
return processedTabFamily;
|
||||||
});
|
}, persistent(Object.keys(tabs)[0]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +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 { deletePersistent, persistent } from "game/persistence";
|
||||||
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -76,16 +76,18 @@ export type GenericTreeNode = Replace<
|
||||||
export function createTreeNode<T extends TreeNodeOptions>(
|
export function createTreeNode<T extends TreeNodeOptions>(
|
||||||
optionsFunc: () => T & ThisType<TreeNode<T>>
|
optionsFunc: () => T & ThisType<TreeNode<T>>
|
||||||
): TreeNode<T> {
|
): TreeNode<T> {
|
||||||
|
const forceTooltip = persistent(false);
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const treeNode: T & Partial<BaseTreeNode> = optionsFunc();
|
const treeNode: T & Partial<BaseTreeNode> = optionsFunc();
|
||||||
treeNode.id = getUniqueID("treeNode-");
|
treeNode.id = getUniqueID("treeNode-");
|
||||||
treeNode.type = TreeNodeType;
|
treeNode.type = TreeNodeType;
|
||||||
|
|
||||||
if (treeNode.tooltip) {
|
if (treeNode.tooltip) {
|
||||||
treeNode.forceTooltip = persistent(false);
|
treeNode.forceTooltip = forceTooltip;
|
||||||
} else {
|
} else {
|
||||||
// If we don't have a tooltip, no point in making this persistent
|
// If we don't have a tooltip, no point in making this persistent
|
||||||
treeNode.forceTooltip = ref(false);
|
treeNode.forceTooltip = ref(false);
|
||||||
|
deletePersistent(forceTooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
processComputable(treeNode as T, "visibility");
|
processComputable(treeNode as T, "visibility");
|
||||||
|
|
|
@ -23,7 +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";
|
import { persistent, Persistent, PersistentState } from "game/persistence";
|
||||||
|
|
||||||
export const UpgradeType = Symbol("Upgrade");
|
export const UpgradeType = Symbol("Upgrade");
|
||||||
|
|
||||||
|
@ -80,9 +80,10 @@ export type GenericUpgrade = Replace<
|
||||||
export function createUpgrade<T extends UpgradeOptions>(
|
export function createUpgrade<T extends UpgradeOptions>(
|
||||||
optionsFunc: () => T & ThisType<Upgrade<T>>
|
optionsFunc: () => T & ThisType<Upgrade<T>>
|
||||||
): Upgrade<T> {
|
): Upgrade<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const upgrade: T & Partial<BaseUpgrade> = optionsFunc();
|
// Create temp literally just to avoid explicitly assigning types
|
||||||
makePersistent<boolean>(upgrade, false);
|
const temp = Object.assign(persistent, optionsFunc());
|
||||||
|
const upgrade: Partial<BaseUpgrade> & typeof temp = temp;
|
||||||
upgrade.id = getUniqueID("upgrade-");
|
upgrade.id = getUniqueID("upgrade-");
|
||||||
upgrade.type = UpgradeType;
|
upgrade.type = UpgradeType;
|
||||||
upgrade[Component] = UpgradeComponent;
|
upgrade[Component] = UpgradeComponent;
|
||||||
|
@ -167,7 +168,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return upgrade as unknown as Upgrade<T>;
|
return upgrade as unknown as Upgrade<T>;
|
||||||
});
|
}, persistent<boolean>(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupAutoPurchase(
|
export function setupAutoPurchase(
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { createLazyProxy } from "util/proxies";
|
||||||
import { createNanoEvents, Emitter } from "nanoevents";
|
import { createNanoEvents, Emitter } from "nanoevents";
|
||||||
import { InjectionKey, Ref, ref, unref } from "vue";
|
import { InjectionKey, Ref, ref, unref } from "vue";
|
||||||
import { globalBus } from "./events";
|
import { globalBus } from "./events";
|
||||||
import { persistent, PersistentRef } from "./persistence";
|
import { Persistent, persistent } from "./persistence";
|
||||||
import player from "./player";
|
import player from "./player";
|
||||||
|
|
||||||
export interface FeatureNode {
|
export interface FeatureNode {
|
||||||
|
@ -58,7 +58,6 @@ export interface Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LayerOptions {
|
export interface LayerOptions {
|
||||||
id: string;
|
|
||||||
color?: Computable<string>;
|
color?: Computable<string>;
|
||||||
display: Computable<CoercableComponent>;
|
display: Computable<CoercableComponent>;
|
||||||
classes?: Computable<Record<string, boolean>>;
|
classes?: Computable<Record<string, boolean>>;
|
||||||
|
@ -70,7 +69,8 @@ export interface LayerOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseLayer {
|
export interface BaseLayer {
|
||||||
minimized: PersistentRef<boolean>;
|
id: string;
|
||||||
|
minimized: Persistent<boolean>;
|
||||||
emitter: Emitter<LayerEvents>;
|
emitter: Emitter<LayerEvents>;
|
||||||
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
||||||
emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
|
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"]>;
|
display: GetComputableType<T["display"]>;
|
||||||
classes: GetComputableType<T["classes"]>;
|
classes: GetComputableType<T["classes"]>;
|
||||||
style: GetComputableType<T["style"]>;
|
style: GetComputableType<T["style"]>;
|
||||||
name: GetComputableTypeWithDefault<T["name"], T["id"]>;
|
name: GetComputableTypeWithDefault<T["name"], string>;
|
||||||
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
|
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
|
||||||
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
|
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
|
||||||
forceHideGoBack: GetComputableType<T["forceHideGoBack"]>;
|
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>(
|
export function createLayer<T extends LayerOptions>(
|
||||||
|
id: string,
|
||||||
optionsFunc: (() => T) & ThisType<BaseLayer>
|
optionsFunc: (() => T) & ThisType<BaseLayer>
|
||||||
): Layer<T> {
|
): Layer<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
|
@ -109,10 +112,19 @@ export function createLayer<T extends LayerOptions>(
|
||||||
layer.on = emitter.on.bind(emitter);
|
layer.on = emitter.on.bind(emitter);
|
||||||
layer.emit = emitter.emit.bind(emitter);
|
layer.emit = emitter.emit.bind(emitter);
|
||||||
layer.nodes = ref({});
|
layer.nodes = ref({});
|
||||||
|
layer.id = id;
|
||||||
|
|
||||||
|
addingLayers.push(id);
|
||||||
|
persistentRefs[id] = new Set();
|
||||||
layer.minimized = persistent(false);
|
layer.minimized = persistent(false);
|
||||||
|
|
||||||
Object.assign(layer, optionsFunc.call(layer));
|
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, "color");
|
||||||
processComputable(layer as T, "display");
|
processComputable(layer as T, "display");
|
||||||
|
|
|
@ -3,10 +3,12 @@ import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import { ProxyState } from "util/proxies";
|
import { ProxyState } from "util/proxies";
|
||||||
import { isArray } from "@vue/shared";
|
import { isArray } from "@vue/shared";
|
||||||
import { isReactive, isRef, Ref, ref } from "vue";
|
import { isReactive, isRef, Ref, ref } from "vue";
|
||||||
import { GenericLayer } from "./layers";
|
import { addingLayers, GenericLayer, persistentRefs } from "./layers";
|
||||||
|
|
||||||
export const PersistentState = Symbol("PersistentState");
|
export const PersistentState = Symbol("PersistentState");
|
||||||
export const DefaultValue = Symbol("DefaultValue");
|
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
|
// Note: This is a union of things that should be safely stringifiable without needing
|
||||||
// special processes for knowing what to load them in as
|
// special processes for knowing what to load them in as
|
||||||
|
@ -20,31 +22,52 @@ export type State =
|
||||||
| { [key: string]: State }
|
| { [key: string]: State }
|
||||||
| { [key: number]: State };
|
| { [key: number]: State };
|
||||||
|
|
||||||
export type Persistent<T extends State = State> = {
|
export type Persistent<T extends State = State> = Ref<T> & {
|
||||||
[PersistentState]: Ref<T>;
|
[PersistentState]: Ref<T>;
|
||||||
[DefaultValue]: 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 = (
|
const persistent = (
|
||||||
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
|
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
|
||||||
) as PersistentRef<T>;
|
) as Persistent<T>;
|
||||||
|
|
||||||
persistent[PersistentState] = persistent;
|
persistent[PersistentState] = persistent;
|
||||||
persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
|
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>(
|
export function deletePersistent(persistent: Persistent) {
|
||||||
obj: unknown,
|
if (addingLayers.length === 0) {
|
||||||
defaultValue: T
|
console.warn("Deleting a persistent ref outside of a layer. Ignoring...", persistent);
|
||||||
): asserts obj is Persistent<T> {
|
}
|
||||||
const persistent = obj as Partial<Persistent<T>>;
|
persistentRefs[addingLayers[addingLayers.length - 1]].delete(persistent);
|
||||||
const state = ref(defaultValue) as Ref<T>;
|
persistent[Deleted] = true;
|
||||||
|
|
||||||
persistent[PersistentState] = state;
|
|
||||||
persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => {
|
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 (value && typeof value === "object") {
|
||||||
if (PersistentState in value) {
|
if (PersistentState in value) {
|
||||||
foundPersistent = true;
|
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
|
// Construct save path if it doesn't exist
|
||||||
const persistentState = path.reduce<Record<string, unknown>>((acc, curr) => {
|
const persistentState = path.reduce<Record<string, unknown>>((acc, curr) => {
|
||||||
|
@ -122,4 +158,12 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
return foundPersistent;
|
return foundPersistent;
|
||||||
};
|
};
|
||||||
handleObject(layer);
|
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
|
// Takes a function that returns an object and pretends to be that object
|
||||||
// Note that the object is lazily calculated
|
// Note that the object is lazily calculated
|
||||||
export function createLazyProxy<T extends object>(objectFunc: () => T): T {
|
export function createLazyProxy<T extends object, S>(
|
||||||
const obj: T | Record<string, never> = {};
|
objectFunc: (baseObject: S) => T & S,
|
||||||
|
baseObject: S = {} as S
|
||||||
|
): T {
|
||||||
|
const obj: S & Partial<T> = baseObject;
|
||||||
let calculated = false;
|
let calculated = false;
|
||||||
function calculateObj(): T {
|
function calculateObj(): T {
|
||||||
if (!calculated) {
|
if (!calculated) {
|
||||||
Object.assign(obj, objectFunc());
|
Object.assign(obj, objectFunc(obj));
|
||||||
calculated = true;
|
calculated = true;
|
||||||
}
|
}
|
||||||
return obj as T;
|
return obj as S & T;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Proxy(obj, {
|
return new Proxy(obj, {
|
||||||
|
@ -51,10 +54,10 @@ export function createLazyProxy<T extends object>(objectFunc: () => T): T {
|
||||||
},
|
},
|
||||||
getOwnPropertyDescriptor(target, key) {
|
getOwnPropertyDescriptor(target, key) {
|
||||||
if (!calculated) {
|
if (!calculated) {
|
||||||
Object.assign(obj, objectFunc());
|
Object.assign(obj, objectFunc(obj));
|
||||||
calculated = true;
|
calculated = true;
|
||||||
}
|
}
|
||||||
return Object.getOwnPropertyDescriptor(target, key);
|
return Object.getOwnPropertyDescriptor(target, key);
|
||||||
}
|
}
|
||||||
}) as T;
|
}) as S & T;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue