State Shapes
There is no single correct way to structure state.
Reheaven gives you the flexibility to choose between many small signals, structured table signals, or a mix of both.
Good state shapes make your code easier to understand, maintain, and scale(yes scability is very important).
Small Signals
A common approach is to store each value separately.
local health, setHealth = Reheaven.signal(100)
local mana, setMana = Reheaven.signal(50)
local level, setLevel = Reheaven.signal(1)
This makes it easy to see what each piece of state represents.
It also allows values to change independently.
Structured Signals
Sometimes several values naturally belong together.
In those cases, a table signal can make sense.
local player, setPlayer = Reheaven.signal({
health = 100;
mana = 50;
level = 1;
})
Now all player-related values live in one place.
print(player().health)
print(player().mana)
Updating Table State
When a signal stores a table, avoid modifying that table directly.
Bad:
local currentPlayer = player()
currentPlayer.coins += 50
The value inside the table changed, but the signal itself was never updated.
Reheaven tracks signal values, not arbitrary mutations that happen somewhere inside a table.
Instead, create a new table and update the signal with that new value.
local currentPlayer = player()
local nextPlayer = table.clone(currentPlayer)
nextPlayer.coins += 50
setPlayer(nextPlayer)
This creates a new state object and clearly tells Reheaven that the state has changed.
A common pattern is:
local current = player()
local nextState = table.clone(current)
-- modify nextState here
setPlayer(nextState)
This becomes especially useful as table state grows larger.
table.clonetable.clone only copies the first level of a table.
That is usually enough for small, flat state tables:
local current = player()
local nextState = table.clone(current)
nextState.coins += 50
setPlayer(nextState)
If you need to change nested tables, use a deepClone helper or split that nested data into its own signal.
local inventory, setInventory = Reheaven.signal({})
local stats, setStats = Reheaven.signal({})
local settings, setSettings = Reheaven.signal({})
Cloning small state tables is usually cheap enough for normal gameplay and UI state.
If a table becomes huge or updates extremely often, consider splitting it into smaller signals instead of cloning the entire table every time.
When using table signals, think about replacing state rather than mutating state.
Read the current state, create the next state, then update the signal.
Current State
↓
Clone
↓
Modify Clone
↓
Set New State
This keeps updates predictable and easier to manage.
Choosing Between Them
Neither approach is always better.
Small signals are often easier to manage:
local health, setHealth = Reheaven.signal(100)
local mana, setMana = Reheaven.signal(50)
Structured signals can be more convenient when values naturally belong together:
local player, setPlayer = Reheaven.signal({
health = 100;
mana = 50;
})
Choose the shape that makes the most sense for your system.
Avoid Giant State Objects
A common mistake is putting everything into one large signal.
local gameState, setGameState = Reheaven.signal({
player = {};
inventory = {};
settings = {};
ui = {};
quests = {};
stats = {};
})
This often becomes difficult to navigate as a project grows.
Instead, consider splitting unrelated state into separate signals.
local player, setPlayer = Reheaven.signal(...)
local inventory, setInventory = Reheaven.signal(...)
local settings, setSettings = Reheaven.signal(...)
Think in Domains
A useful guideline is to group state by responsibility.
For example:
Player State
Inventory State
UI State
Settings State
Round State
Each group can then decide whether it should use one signal or many.
Next Steps
Next, learn how actions can help organize and centralize state changes as your project grows.