-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Description
Describe the problem
Problem
I'm finding a lot of use out of separating portions of state into different objects and combining them as needed. For example, I am working on a project for playing chess with variants. Every single variant is going to have some number of state properties, but also some state specific to that variant.
In order for derived values and state values to exist on the same object, we have to return an object with getters and setters (AFAIK).
So, the base state can be created as follows:
export const createBaseChessState = (moveValidator: MoveValidationService): T_BaseBoardState => {
let pov: Color = $state('w')
let selectedSquare: Square | null = $state(null);
let handlePromotion: ((piece: PieceSymbol) => void) | null = $state(null);
let validMoveSquares: Square[] = $derived.by(() => {
if (selectedSquare === null) {
return [];
}
return moveValidator.getValidMoves(selectedSquare).map(move => move.to);
});
let showPromotionDialog: boolean = $derived(handlePromotion !== null);
let currentBoard: Board = $state([]);
let checkSquare: Square | null = $state(null);
return {
get pov() {
return pov;
},
set pov(value: Color) {
pov = value;
},
get selectedSquare() {
return selectedSquare;
},
set selectedSquare(value: Square | null) {
selectedSquare = value;
},
get handlePromotion() {
return handlePromotion;
},
set handlePromotion(value: ((piece: PieceSymbol) => void) | null) {
handlePromotion = value;
},
get validMoveSquares() {
return validMoveSquares;
},
get showPromotionDialog() {
return showPromotionDialog;
},
get currentBoard() {
return currentBoard;
},
set currentBoard(value: Board) {
currentBoard = value;
},
get checkSquare() {
return checkSquare;
},
set checkSquare(value: Square | null) {
checkSquare = value;
},
}
}
and then for something like standard chess, you will also have some more specific state:
let isCheckmate: boolean = $state(false);
let isStalemate: boolean = $state(false);
let isDraw: boolean = $state(false);
let isThreefoldRepetition: boolean = $state(false);
let isInsufficientMaterial: boolean = $state(false);
let isGameOver: boolean = $state(false);
Now, if I want all this state on the same object, I'm going to have to something like this:
export const createStandardChessState = (moveValidator: MoveValidationService): StandardChessState => {
const baseState = createBaseChessState(moveValidator);
let isCheckmate: boolean = $state(false);
let isStalemate: boolean = $state(false);
let isDraw: boolean = $state(false);
let isThreefoldRepetition: boolean = $state(false);
let isInsufficientMaterial: boolean = $state(false);
let isGameOver: boolean = $state(false);
return {
get pov() {
return baseState.pov;
},
set pov(value: Color) {
baseState.pov = value;
},
get selectedSquare() {
return baseState.selectedSquare;
},
set selectedSquare(value: Square | null) {
baseState.selectedSquare = value;
},
get handlePromotion() {
return baseState.handlePromotion;
},
set handlePromotion(value: ((piece: PieceSymbol) => void) | null) {
baseState.handlePromotion = value;
},
get validMoveSquares() {
return baseState.validMoveSquares;
},
get showPromotionDialog() {
return baseState.showPromotionDialog;
},
get currentBoard() {
return baseState.currentBoard;
},
set currentBoard(value: Board) {
baseState.currentBoard = value;
},
get checkSquare() {
return baseState.checkSquare;
},
set checkSquare(value: Square | null) {
baseState.checkSquare = value;
},
get isCheckmate() {
return isCheckmate;
},
set isCheckmate(value: boolean) {
isCheckmate = value;
},
get isStalemate() {
return isStalemate;
},
set isStalemate(value: boolean) {
isStalemate = value;
},
get isDraw() {
return isDraw;
},
set isDraw(value: boolean) {
isDraw = value;
},
get isThreefoldRepetition() {
return isThreefoldRepetition;
},
set isThreefoldRepetition(value: boolean) {
isThreefoldRepetition = value;
},
get isInsufficientMaterial() {
return isInsufficientMaterial;
},
set isInsufficientMaterial(value: boolean) {
isInsufficientMaterial = value;
},
get isGameOver() {
return isGameOver;
},
set isGameOver(value: boolean) {
isGameOver = value;
}
}
}
This is clearly a lot of code for the task. I'm not an expert, but I wonder if there is also a performance hit associated with this kind of stuff that might not be a problem if handled at compile time.
Why would you want to do this?
I find there is a benefit in that I can create specific types for different pieces of state without constructing complex nested state objects. Additionally, having derived and state values on the same object is desirable in many cases.
Describe the proposed solution
It's hard to say for sure, as I don't know exactly how the internals of svelte 5 work, but something like a rune that makes it easier to combine state.
export const createBaseChessState = (moveValidator: MoveValidationService): T_BaseBoardState => {
let pov: Color = $state('w')
let selectedSquare: Square | null = $state(null);
let handlePromotion: ((piece: PieceSymbol) => void) | null = $state(null);
let validMoveSquares: Square[] = $derived.by(() => {
if (selectedSquare === null) {
return [];
}
return moveValidator.getValidMoves(selectedSquare).map(move => move.to);
});
let showPromotionDialog: boolean = $derived(handlePromotion !== null);
let currentBoard: Board = $state([]);
let checkSquare: Square | null = $state(null);
const state = $restate(
pov,
selectedSquare,
handlePromotion,
validMoveSquare,
showPromotionDialog,
currentBoard,
checkSquare
)
return state;
}
export const createStandardChessState = (moveValidator: MoveValidationService): StandardChessState => {
const baseState = createBaseChessState(moveValidator);
let isCheckmate: boolean = $state(false);
let isStalemate: boolean = $state(false);
let isDraw: boolean = $state(false);
let isThreefoldRepetition: boolean = $state(false);
let isInsufficientMaterial: boolean = $state(false);
let isGameOver: boolean = $state(false);
const combinedState = $restate(
baseState,
isCheckmate,
isStalemate,
isDraw,
isThreefoldRepetition,
isInsufficientMaterial,
isGameOver
)
return combinedState;
}
$restate
might be confusing, so there could be $merge
, $compose
, $combine
Importance
would make my life easier