Appearance
Client Functions
Client-side PlayerState API for data access and change listeners.
Client Only - READ-ONLY
Client scripts only. These functions are READ-ONLY - no data modification!
Need to change data? Create your own RemoteEvents with server validation.
Data Access Functions
Get()
Get(key)
Gets a top-level data value. Automatically waits for data to load with built-in caching for performance.
Parameters:
key: string
- Data key
Returns: any
- The value, or nil
if not found
Example:
lua
local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)
local coins = PlayerState.Get("Coins")
local level = PlayerState.Get("Level")
print("Player has", coins, "coins and is level", level)
Notes:
- Automatically waits for server data to sync - no manual waiting needed!
- Returns
nil
if the key doesn't exist - Thread-safe - can be called from any context
- NEW: Built-in caching system for improved performance
- NEW: Automatic cache cleanup to prevent memory leaks
GetPath()
GetPath(path)
Gets a nested data value using dot notation. Automatically waits for data to load with optimized nested value caching.
Parameters:
path: ValuePath
- Dot-separated path (e.g., "Plot.Likes")
Returns: any
- The value, or nil
if not found
Example:
lua
local likes = PlayerState.GetPath("Plot.Likes")
local musicEnabled = PlayerState.GetPath("Settings.MusicEnabled")
local highScore = PlayerState.GetPath("Stats.HighScore")
print("Plot has", likes, "likes")
print("Music is", musicEnabled and "enabled" or "disabled")
Notes:
- Handles nested data structures safely
- Returns
nil
if any part of the path doesn't exist - Supports unlimited nesting depth
- NEW: Optimized nested value caching with automatic cleanup
- NEW: Improved path parsing performance
GetAll()
GetAll()
Gets all player data. Automatically waits for data to load.
Parameters: None
Returns: PlayerData?
- Complete data table, or nil
if not loaded
Example:
lua
local allData = PlayerState.GetAll()
if allData then
print("Complete player data:", allData)
-- Iterate through top-level keys
for key, value in pairs(allData) do
print(key, "=", value)
end
end
Notes:
- Returns a reference to the actual data (do not modify!)
- Useful for debugging or displaying all data
- Consider performance when accessing large datasets
Read-Only Data
Don't modify the returned data! It's read-only on the client.
GetFromDict()
GetFromDict(dictPath, key)
Gets a value from a dictionary using a key with automatic type conversion and caching.
Parameters:
dictPath: ValuePath
- Path to the dictionarykey: string | number
- Dictionary key (numbers are converted to strings)
Returns: any
- The value, or nil
if not found
Example:
lua
local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)
-- Get building data
local houseData = PlayerState.GetFromDict("Plot.Buildings", "House")
if houseData then
print("House level:", houseData.Level)
print("House material:", houseData.Material)
end
-- Get with numeric key (automatically converted to string)
local equippedItem = PlayerState.GetFromDict("Inventory.Equipped", 1)
-- Get setting value
local quality = PlayerState.GetFromDict("Settings", "GraphicsQuality")
print("Graphics quality:", quality or "Default")
Notes:
- Automatically waits for server data to sync - no manual waiting needed!
- Automatically converts number keys to strings for consistency
- Returns
nil
if the dictionary or key doesn't exist - NEW: Built-in caching for improved performance
- NEW: Enhanced type handling and validation
Status Functions
IsReady()
IsReady()
Checks if player data is ready and replica is active.
Parameters: None
Returns: boolean
- True if data is ready, false otherwise
Example:
lua
if PlayerState.IsReady() then
local coins = PlayerState.Get("Coins")
print("Player has", coins, "coins")
else
print("Data not ready yet...")
end
Notes:
- NEW: Optional check - data access functions already wait automatically
- NEW: Useful for conditional logic or bulk operations
- NEW: More reliable than checking if
GetAll()
returns nil
Cache Management Functions
ClearCache()
ClearCache()
Manually clears all internal caches. Useful for memory management or debugging.
Parameters: None
Returns: Nothing
Example:
lua
-- Clear cache when switching players or for debugging
PlayerState.ClearCache()
-- Cache will rebuild automatically on next access
local coins = PlayerState.Get("Coins")
Notes:
- NEW: Manual cache management
- Cache automatically rebuilds on next data access
- Use sparingly - caching improves performance
- Useful for debugging cache-related issues
Change Listener Functions
OnChanged()
OnChanged(pathOrKey, callback)
Listens for changes to a specific data path or key with enhanced change information.
Parameters:
pathOrKey: string
- Data key, dot-separated path, or"."
for all changescallback: (newValue: any, oldValue: any, changeInfo: ChangeInfo | {string}?) -> ()
- Function called when data changes
Returns: ReplicaClient.Connection?
- Connection to disconnect the listener
lua
-- Listen to top-level changes
local coinsConnection = PlayerState.OnChanged("Coins", function(newValue, oldValue)
print("Coins changed from", oldValue, "to", newValue)
updateCoinsUI(newValue)
end)
lua
-- Listen to nested changes
local likesConnection = PlayerState.OnChanged("Plot.Likes", function(newValue, oldValue, pathInfo)
print("Likes changed:", newValue)
if pathInfo then
print("Full path:", table.concat(pathInfo, "."))
end
updateLikesDisplay(newValue)
end)
lua
-- Listen to ALL changes with detailed action info
local allChangesConnection = PlayerState.OnChanged(".", function(newValue, oldValue, changeInfo)
if changeInfo and changeInfo.action then
local pathString = table.concat(changeInfo.path, ".")
print(`{changeInfo.action}: {pathString} = {newValue} (was {oldValue})`)
-- Handle different action types
if changeInfo.action == "TableInsert" then
print("Item added at index:", changeInfo.index)
elseif changeInfo.action == "TableRemove" then
print("Item removed from index:", changeInfo.index)
end
end
end)
Enhanced Change Information:
- NEW:
ChangeInfo
type provides detailed action information - NEW: Support for
"Set"
,"SetValues"
,"TableInsert"
,"TableRemove"
actions - NEW: Index information for array operations
- NEW: Full path information for all changes
Notes:
- Callbacks fire immediately when data changes
- Multiple listeners can be attached to the same path
- Remember to disconnect when no longer needed
- NEW: Use
"."
to listen for all data changes with action details - NEW: Automatic cache invalidation on changes
- NEW: Enhanced connection management and cleanup
Managing Connections
lua
-- Store connections for cleanup
local connections = {}
connections.coins = PlayerState.OnChanged("Coins", updateCoinsUI)
connections.level = PlayerState.OnChanged("Level", updateLevelUI)
-- NEW: Global listener with action handling
connections.all = PlayerState.OnChanged(".", function(newValue, oldValue, info)
if info and info.action == "TableInsert" then
print("Array item added!")
end
end)
-- Cleanup when done
for name, connection in pairs(connections) do
if connection then
connection:Disconnect()
end
end
Advanced Functions
GetReplica()
GetReplica()
Gets the raw Replica instance for advanced operations.
Parameters: None
Returns: ReplicaInstance?
- Replica instance, or nil
if not loaded
Example:
lua
local replica = PlayerState.GetReplica()
if replica then
-- Access raw Replica API
print("Replica data:", replica.Data)
print("Replica tags:", replica.Tags)
-- Advanced change listener
replica:OnChange(function(action, path, param1, param2)
print("Raw change:", action, table.concat(path, "."))
end)
end
Notes:
- Provides access to the underlying Replica object
- Useful for advanced features not exposed by PlayerState
- Only use if you need functionality not provided by the main API
- NEW: Enhanced validation ensures replica is active
Usage Examples
UI Updates with Enhanced Change Handling
lua
local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)
-- UI Elements
local coinsLabel = gui.CoinsLabel
local levelLabel = gui.LevelLabel
local experienceBar = gui.ExperienceBar
-- Initial UI setup (data access automatically waits)
local function updateUI()
coinsLabel.Text = tostring(PlayerState.Get("Coins") or 0)
levelLabel.Text = "Level " .. tostring(PlayerState.Get("Level") or 1)
local exp = PlayerState.Get("Experience") or 0
local maxExp = (PlayerState.Get("Level") or 1) * 100
experienceBar.Size = UDim2.new(exp / maxExp, 0, 1, 0)
end
-- Listen for changes with enhanced info
local connections = {}
connections.coins = PlayerState.OnChanged("Coins", function(newValue, oldValue, info)
coinsLabel.Text = tostring(newValue or 0)
-- Show gain/loss animation
if oldValue and newValue > oldValue then
showGainAnimation(newValue - oldValue)
end
end)
connections.level = PlayerState.OnChanged("Level", function(newValue, oldValue, info)
levelLabel.Text = "Level " .. tostring(newValue or 1)
updateUI() -- Recalculate experience bar
if oldValue and newValue > oldValue then
showLevelUpEffect()
end
end)
-- NEW: Global change listener for debugging
connections.debug = PlayerState.OnChanged(".", function(newValue, oldValue, info)
if info and info.action then
print(`[DEBUG] {info.action} at {table.concat(info.path, ".")}`)
end
end)
-- Initial update
updateUI()
-- Enhanced cleanup function
local function cleanup()
for name, connection in pairs(connections) do
if connection and typeof(connection) == "table" and connection.Disconnect then
connection:Disconnect()
end
end
connections = {}
end
Settings Management
lua
-- Settings UI manager with caching
local SettingsManager = {}
local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)
local SoundService = game:GetService("SoundService")
function SettingsManager.init()
-- Apply initial settings (automatically waits for data)
local musicEnabled = PlayerState.GetPath("Settings.MusicEnabled")
local soundEnabled = PlayerState.GetPath("Settings.SoundEnabled")
SoundService.AmbientReverb = musicEnabled and Enum.ReverbType.City or Enum.ReverbType.NoReverb
SoundService.Volume = soundEnabled and 1 or 0
-- Listen for changes
PlayerState.OnChanged("Settings.MusicEnabled", function(enabled)
SoundService.AmbientReverb = enabled and Enum.ReverbType.City or Enum.ReverbType.NoReverb
end)
PlayerState.OnChanged("Settings.SoundEnabled", function(enabled)
SoundService.Volume = enabled and 1 or 0
end)
end
return SettingsManager
Inventory Display with Array Change Handling
lua
-- Inventory UI manager with enhanced change detection
local InventoryUI = {}
local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)
-- UI elements
local inventoryFrame = gui.InventoryFrame
local itemTemplate = inventoryFrame.ItemTemplate
function InventoryUI.updateDisplay()
-- Clear existing items
for _, child in pairs(inventoryFrame:GetChildren()) do
if child ~= itemTemplate and child:IsA("Frame") then
child:Destroy()
end
end
-- Get inventory data
local inventory = PlayerState.GetPath("Inventory") or {}
-- Create UI elements for each item
for index, item in ipairs(inventory) do
local itemFrame = itemTemplate:Clone()
itemFrame.Name = "Item" .. index
itemFrame.Visible = true
itemFrame.Parent = inventoryFrame
-- Update item display
itemFrame.ItemName.Text = item.Name or "Unknown"
itemFrame.Rarity.Text = item.Rarity or "Common"
itemFrame.LayoutOrder = index
end
end
function InventoryUI.init()
-- Initial display (automatically waits for data)
InventoryUI.updateDisplay()
-- NEW: Enhanced inventory change listener
PlayerState.OnChanged("Inventory", function(newValue, oldValue, info)
InventoryUI.updateDisplay()
-- Handle specific actions
if info and info.action == "TableInsert" then
showItemAddedEffect(info.index)
elseif info and info.action == "TableRemove" then
showItemRemovedEffect(info.index)
end
end)
end
return InventoryUI
Error Handling
Safe Data Access with Readiness Checks
lua
-- Safe data access (automatically waits for data)
local function getPlayerData()
return {
coins = PlayerState.Get("Coins") or 0,
level = PlayerState.Get("Level") or 1,
likes = PlayerState.GetPath("Plot.Likes") or 0
}
end
-- Safe nested access with validation
local function getInventoryItem(index)
local inventory = PlayerState.GetPath("Inventory")
if not inventory or typeof(inventory) ~= "table" then
return nil
end
return inventory[index]
end
-- Check if specific data exists
local function hasValidInventory()
local inventory = PlayerState.GetPath("Inventory")
return inventory and typeof(inventory) == "table" and #inventory > 0
end
Enhanced Connection Management
lua
-- Advanced connection management system
local ConnectionManager = {}
local activeConnections = {}
function ConnectionManager.add(name, connection)
-- Cleanup existing connection
if activeConnections[name] then
if typeof(activeConnections[name]) == "table" and activeConnections[name].Disconnect then
activeConnections[name]:Disconnect()
end
end
activeConnections[name] = connection
end
function ConnectionManager.remove(name)
local connection = activeConnections[name]
if connection and typeof(connection) == "table" and connection.Disconnect then
connection:Disconnect()
end
activeConnections[name] = nil
end
function ConnectionManager.cleanup()
for name, connection in pairs(activeConnections) do
if connection and typeof(connection) == "table" and connection.Disconnect then
connection:Disconnect()
end
end
activeConnections = {}
end
-- Usage with enhanced error handling
ConnectionManager.add("coins", PlayerState.OnChanged("Coins", function(newValue, oldValue, info)
if typeof(newValue) == "number" then
updateCoinsDisplay(newValue)
else
warn("Invalid coins value received:", newValue)
end
end))
-- Cleanup when done
ConnectionManager.cleanup()
Performance Tips
🚀 Caching and Optimization
- Automatic Caching: The client now uses built-in caching - frequently accessed values are cached automatically
- Cache Management: Use
ClearCache()
only when necessary (debugging, memory concerns) - Readiness Checks: Use
IsReady()
before bulk operations to avoid unnecessary waits - Specific Listeners: Use specific paths instead of global
"."
listeners when possible - Connection Cleanup: Always disconnect listeners to prevent memory leaks
🔧 Best Practices
- Always provide fallbacks when accessing data that might not exist
- Data access automatically waits - no manual waiting required!
- Use IsReady() only for conditional logic or optimization
- Disconnect listeners when UI elements are destroyed or no longer needed
- Use specific paths instead of listening to all changes when possible
- Handle different action types in global listeners for better user experience
- Don't modify returned data - it's a reference to the actual data structure