Skip to content

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 dictionary
  • key: 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 changes
  • callback: (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

  1. Automatic Caching: The client now uses built-in caching - frequently accessed values are cached automatically
  2. Cache Management: Use ClearCache() only when necessary (debugging, memory concerns)
  3. Readiness Checks: Use IsReady() before bulk operations to avoid unnecessary waits
  4. Specific Listeners: Use specific paths instead of global "." listeners when possible
  5. Connection Cleanup: Always disconnect listeners to prevent memory leaks

🔧 Best Practices

  1. Always provide fallbacks when accessing data that might not exist
  2. Data access automatically waits - no manual waiting required!
  3. Use IsReady() only for conditional logic or optimization
  4. Disconnect listeners when UI elements are destroyed or no longer needed
  5. Use specific paths instead of listening to all changes when possible
  6. Handle different action types in global listeners for better user experience
  7. Don't modify returned data - it's a reference to the actual data structure

PlayerState - High-Performance Roblox Data Management