Saving & Loading Data
Overview
Use DataStore to save and retrieve core data, such as the player’s level, EXP, and current gold. This allows you to maintain the player’s progress over time and develop games with growth mechanics, such as in RPGs.
How to Use
Supported Data Types
number
O
string
O
bool
O
table
O
object
Not supported.
Functions
Not supported.
Default Structure
Use the GetDataStore function of DataStoreService to retrieve DataStore objects with a designated name. You can save or retrieve data using a key-value format within the DataStore object.
Key: Unique name that identifies data (e.g. PlayerGold)
Value: Data to be saved (e.g. 1,000)
Additionally, you can store additional information for the key:
UserIds (Array): A list of UserIds related to the data.
Metadata (Table): Additional metadata information to be stored.
These two additional fields can be retrieved using DataStoreKeyInfo.
GetDataStore(name)
Retrieve Datastore object that corresponds to the name
GetAsync(key)
Retrieve data that corresponds to the key in the Datastore object
SetAsync(key, value)
Save (overwrite) data in the key in the Datastore object
IncrementAsync(key, delta, userIds(*optional), datastoreSetOption(*optional))
Increases/Decreases the data corresponding to the key in the Datastore object (only works for number type data)
UpdateAsync(key, callback)
Updates the data corresponding to the key in the Datastore object through the callback
RemoveAsync(key)
Deletes the data corresponding to the key in the Datastore object
Data Store Retrieval
Saving
Retrieving
Updating
When using DataStore to save player data, simply relying on GetAsync and SetAsync can lead to race conditions when multiple users are reading and writing data simultaneously. This can result in one user’s saved value being overwritten by another user’s save request, or data loss occurring in some cases.
For example, while fetching a value with GetAsync and processing it, if another event or server call triggers SetAsync and updates the value, there is no way to verify if the value was updated during processing. As a result, the value calculated based on the outdated value could overwrite the most recent update, causing the previous changes to be lost.
To prevent such race conditions and ensure data integrity, a more secure data handling approach is required.
IncrementAsync
For cases where you simply need to increase (or decrease) a numerical value, IncrementAsync can be used. IncrementAsync supports atomic operations internally, ensuring that multiple users can safely increment the value simultaneously without data conflicts. Therefore, for number data that requires simple accumulation, such as coins, experience points, or scores, IncrementAsync is the simplest and most efficient choice.
UpdateAsync
However, IncrementAsync is only valid for numeric data types and cannot handle more complex data updates such as multiplication, division, or other conditional logic.
The UpdateAsync function is designed to atomically handle the process of reading, modifying, and saving values in the DataStore. This ensures that even in concurrent access situations, the value can be updated reliably without data loss.
UpdateAsync takes a callback function as an argument, passes the currently stored value to the callback, and then saves the value returned by the callback to the DataStore. If multiple requests come in simultaneously and a data conflict occurs, UpdateAsync automatically fetches the latest value and reruns the callback to resolve the conflict. This process repeats until the data is safely updated.
The callback function enables ACID (Atomicity, Consistency, Isolation, Durability) handling, ensuring the stability and integrity of the data transaction. Based on the returned value, the actual application of the update can be determined. For example, if the callback returns nil, the update will be canceled. This functionality allows for the implementation of conditional data updates, integrity checks, and other complex logic.
Deleting Data
Full Code Example
The following code retrieves data stored on the server when a player enters the game. If no saved value is found, the initial value is set, and this value is assigned as the player’s Attribute.
When the save function is called, the current value of the Attribute is saved to the server.
LoadPlayerDataWhenEnter(player)
Load when the player logs in to the game
DataManager:LoadPlayerData(player)
Retrieves player data saved in the server (GetAsync)
If no saved value exists, the initial value (InitValue) is set and saved to the server.
The retrieved value is set as the player’s Attribute
DataManager:SavePlayerData(player)
Reads the player’s Attribute value and saves it to the server (SetAsync)
Automatically saves when the player exits the game through the PlayerRemoving event.
Usage Example
When saving or retrieving data from DataStore, you can manage both individual user data and global game data depending on how the key value is structured.
This approach allows you to save and retrieve game data across all areas, such as leaderboards, event progress, server settings, etc.
Difference Between Actions in Published and Test Environments
After publishing, data is saved and retrieved on the live server in mobile environments. However, in studio test environments, data is temporarily stored locally. When the studio session ends, this local data is automatically deleted.
Important Notes
If the player starts playing the game before the data is fully loaded, an error may occur. To prevent this, display a loading UI until the data has finished loading.
Design the data to be saved in a structure that is as compact and simple as possible.
Excessive requests can lead to data save failures, so avoid making repeated saves within a short time frame.
API retrieval cannot exceed 150 requests per minute. Exceeding this limit may result in restrictions on the server.
If the game unexpectedly ends or there is a server collision, data may be lost. To prevent this, ensure that data is saved periodically.
Saving and retrieval may fail, so use pcall to prevent errors and add a retry logic.
Last updated