Data Store (Save Feature)
Overview
The DataStore feature provided by ParaSpace enables you to save persistent data independent of rooms, such as players' items, equipment, and skill points. In other words, this feature allows players to save games. DataStore allows consistent data access and storage. Players can access and modify the same data items when they are playing in any room, even if they are accessing rooms in different areas.
Enable DataStore
By default, you can enable DataStore in all the Worlds created by you. Through unified DataStore APIs, you can get DataStore capabilities and services.
If you are using a simulator and your World has not been uploaded to the server, the DataStore feature is not available.
However, if you are using a simulator and your World has already been uploaded to the server, you can choose to connect to a test environment (Sand Box) or an online production environment (Released Data). If you connect to the online production environment, all your subsequent DataStore operations will affect data in that environment. The following figure shows where to enable DataStore if you are using a simulator:
Access DataStore Objects
All DataStore operations are triggered by the ParaDataStore and ParaOrderedDataStore entity classes.
ParaDataStore
Entity class for DataStore operations. Each ParaDataStore class represents a collection of data of the same type. All data stored in ParaDataStore is composed of key-value pairs and supports metadata and version-related operations.
ParaOrderedDataStore
Entity class for DataStore operations. Each ParaOrderedDataStore class represents a collection of data of the same type that is composed of key-value pairs. All data stored in ParaOrderedDataStore can only be of numerical type and doesn't support metadata and version-related operations. Besides, data stored in ParaOrderedDataStore also supports sorting. By default, data is sorted in ascending or descending numerical order based on the values of the data.
Create and Get a ParaDataStore or ParaOrderedDataStore Object
To store and access data, you must create or get a ParaDataStore or ParaOrderedDataStore object. You can call the GetDataStore or GetOrderedDataStore API provided by ParaDataService to create or get a ParaDataStore or ParaOrderedDataStore object. Note that both creating and getting a ParaDataStore or ParaOrderedDataStore object are done by calling these 2 APIs.
local ok, store1 = ParaDataService.GetDataStore(string name, string scope = “global”, bool all_scope = false)
local ok, store2 = ParaDataService.GetOrderedDataStore(string name, string scope = “global”)
The following describes the parameters used by the 2 APIs:
- name: name of a ParaDataStore or ParaOrderedDataStore object.
- scope: specifies the string of the domain name. All the strings are automatically split into tables by domain.
- allScope: specifies whether to access data within all scopes. If the value is true, the obtained DataStore object can access data within different scopes in the same DataStore object. However, you must specify the scopes in the parameter key of the API for obtaining data, that is, "scope:key". Otherwise, the object can only access data within a specified scope. When allScope is set to true, the scope parameter must be left empty or set to nil. When allScope is set to false and no scope is specified in the scope parameter, the default value "global" will be assigned to the scope parameter.
If you are using a simulator and haven't enabled DataStore or don't meet the enabling prerequisites, these APIs will return a corresponding error code and message.
Operate Data
Get Data
To get data, you must comply with the code format rule. That is, you must place DataStore-related code inside the DATASTORE() code block. In other words, a function is passed in for DATASTORE to call. See the following code example.
You can call the GetData API of DataStore to get corresponding data. If the API operation is successful, GetData returns the following 3 values:
- code: numerical type. If the value is 0, the API operation is successful. Otherwise, the API operation fails.
- value: If the API operation is successful, this value equals the value corresponding to the key. If the API operation fails (code is not 0), this value is an error message of string type.
- info: If the API operation is successful (code is 0), then info includes detailed data information with the ParaDataStoreInfo structure. The detailed information includes specific metadata, version data, and specific key values. You can use the following code example to access data and perform subsequent operations.
DATASTORE(
function()
-- Get my_world's bag capacity for local player
local player = CS.ParaPlayerService.GetLocalPlayer()
if player == nil then
print('Local player is nil')
return
end
local ok, store = ParaDataService.GetDataStore('my_world', 'bag_' .. player.UID)
if ok ~= 0 then
print('Load Failed:' .. ok .. tostring(store))
return
end
local ok, bag_capacity, info = store:GetData('capacity')
if ok ~= 0 then
print('Get data failed!, code:' .. ok .. ' error_msg:' .. bag_capacity)
return
end
print('Bag capacity for player:' .. player.UID .. ' is:' .. bag_capacity)
end)
The preceding code sends a request to the server to get the latest data for subsequent use. To access other information such as metadata and version information, you can call the API that returns info. For the specific solution, see the description in subsequent sections.
Set Data
Data can be set through the SetData API of DataStore. The input parameters of the SetData API include key, value, and options. The key parameter represents the data's key, the value parameter represents the data's value, and the options parameter represents additional information. The return value of the SetData API is the same as that of the GetData API.
Actually, data is stored in DataStore in the form of key-value pairs. When data with the same key exists in DataStore, the newly set data replaces the original data with the same key and the original data will be saved in historical versions. However, if no such data exists, a new key will be created and the corresponding value will be saved in that key.
To modify the metadata of that value, you can use the options parameter. For how to use the API, see the description in subsequent sections. Note that the options parameter cannot be used to set metadata and historical version information stored in ParaOrderedDataStore.
Update Data Incrementally
Incremental updates can update data without getting the current value of the data. Compared with SetData, incremental updates can reduce the risk of data inconsistency. For example, if data is modified by another operation during the time between getting data and setting data, calling SetData again could lead to data inconsistency. Incremental updates are generally used for modifying globally unique or locally unique values, or for other values that may be modified simultaneously by multiple servers, such as the total number of players in a room or the overall progress of a task.
You can perform incremental updates through the IncrementData API of DataStore:
ok, value, ParaDataStoreInfo info = IncrementData(string key, number delta, ParaDataSetOptions options = nil)
The following code example shows that the total number of players in the game increases by 1 when a new player joins the game:
DATASTORE(
function()
-- Get my_world's players info
local ok, store = ParaDataService.GetDataStore('my_world', 'players_info')
if ok ~= 0 then
print('Load Failed:' .. ok .. tostring(store))
return
end
ok, message = store:IncrementData('total_player_count', 1)
if ok ~= 0 then
print('Increment data failed!, code:' .. ok .. ' error_msg:' .. message)
return
end
ok, total_players = store:GetData('total_player_count')
if ok ~= 0 then
print('Get data failed!, code:' .. ok .. ' error_msg:' .. total_players)
return
end
print('Total player count is:' .. total_players)
end)
A storage variable is of numerical type. We recommend performing incremental updates on the variable when it is modified simultaneously by multiple servers.
Use a Function to Modify Data
You can modify data using UpdateData.
ok, value, ParaDataStoreInfo info = UpdateData(string key, function updateFunction(currentValue, ParaDataStoreInfo info)
{return newValue, ParaDataSetOptions})
UpdateData can also be used to set data in DataStore. Unlike SetData and IncrementData, UpdateData can pass in a update function. The input of the function is the current value of the key of the data to be updated and the return value is the expected value and additional modification information (such as changes to metadata). This function can simplify the business logic of getting, modifying, and uploading data, reducing the risk of inconsistent data across multiple servers.
The function passed in by UpdateData is:
function updateFunction(currentValue, ParaDataStoreInfo info){return newValue, ParaDataSetOptions options})
The parameter passed in is the current value of the key passed in by UpdateData. The info parameter indicates the additional metadata and version information. The return values of the function are newValue and options.
UpdateData will save the 2 return values into the data corresponding to the key as the new value of the key and additional information. The specific code example is as follows:
DATASTORE(
function()
-- Get my_world's guild info
local ok, store = ParaDataService.GetDataStore('my_world', 'guild_info')
if ok ~= 0 then
print('Load Failed:' .. ok .. tostring(store))
return
end
local ok,message = store:SetData('notice', 'init_version')
if ok ~= 0 then
print('SetData returns:' .. ok .. tostring(message))
return
end
local ok, new_notice_info = store:UpdateData('notice',
function(notice)
print('current notice info is:' .. notice)
local update_time = os.date("%c");
local new_notice = notice .. '\\n' .. update_time .. ':new event info'
local opts = ParaDataService.CreateDataSetOptions()
opts:SetMetadata({latest_update_time = update_time})
return new_notice, opts
end)
if ok == 0 then
print('Update guild notice info to:' .. new_notice_info)
end
end)
IncrementData vs. SetData vs. UpdateData
DataStore provides 3 data update modes. Depending on the usage scenario, you need to select an appropriate upload mode accordingly.
- SetData
SetData is the most basic DataStore API. It is easy to use and suitable for performing basic DataStore operations such as saving player data like levels, points, and login time. However, when you call this API to set data that may be modified simultaneously by multiple servers, data inconsistency may occur. When you use this API to set data like all-server-wide points or game state, you need to prevent data inconsistency caused by simultaneous operations across multiple servers based on the actual business logic of the operation. - IncrementData
In response to potential data inconsistency caused by SetData operations across multiple servers, IncrementData provides the atomic incremental update mode, which can effectively ensure data consistency. However, IncrementData can only operate on numerical data and cannot perform targeted logic processing based on the current value. Therefore, IncrementData is more suitable for updating global numeric fields. It can only handle simple data update logic. For more complex update logic, IncrementData may not be sufficient. - UpdateData
UpdateData is a function-based update API suitable for complex data update scenarios with high consistency requirements. The UpdateData API is difficult to use and its operation process is complex, making it less efficient and concise for general update requests.
Clear Data
You can call the RemoveData API to clear corresponding data. As DataStore supports version management by default, deleted data can be obtained through historical version retrieval in version management. The data is truly removed from the database only when historical versions expire or are manually deleted.
Only ParaDataStore supports version management, while ParaOrderedDataStore does not.
Sort Data
ParaOrderedDataStore can automatically sort data based on data values and get data in pagination mode.
ok, DataStorePages pages = GetSortedData(bool ascending, int pagesize, double min_value = -math.huge, double max_value = math.huge)
ParaOrderedDataStore allows you to get a list of data sorted by value through the GetSortedData API. When calling this API, you can set the number of data items to be displayed on each page as well as the minimum and maximum values to be retrieved. Page numbers start from 0 and increment. The default values for the minimum and maximum values are the minimum and maximum floating-point numbers in Lua respectively. When they are set to nil or no value, you will get all data stored in the DataStore object.
The return value of GetSortedData is an error code and a ParaDataPages object containing pagination information. You can call ParaDataPages APIs to traverse data by page. Relevant APIs will be described in subsequent sections. The following code example gets the items contained in a player's inventory:
DATASTORE(
function()
local page_size = 10
-- Get my_world's arena rank points
local ok, store = ParaDataService.GetOrderedDataStore('my_world', 'arena_rank')
if ok ~= 0 then
print('Load data store failed! code is:' .. ok .. ' message is:' .. store)
return
end
local ok, message = store:SetData('rank_1', 100)
if ok ~= 0 then
print('Set data failed! code is:' .. ok .. ' message is:' .. message)
return
end
local ok, message = store:SetData('rank_2', 80)
if ok ~= 0 then
print('Set data failed! code is:' .. ok .. ' message is:' .. message)
return
end
local ok, pages = store:GetSortedData(true, page_size)
if ok ~= 0 then
print('GetSortedData failed! code is:' .. ok .. ' message is:' .. pages)
return
end
local page_index = 1
repeat
local success, page_values = pages:GetCurrentPage()
local index = 1
if success == 0 then
for k, v in pairs(page_values) do
print('rank:' .. (page_index - 1) * page_size + index ..
' points:' .. tostring(v.value))
index = index + 1
end
end
page_index = page_index + 1
until(not pages:AdvanceToNextPage())
end)
Prefix Search
ParaDataStore supports prefix search based on key and can automatically sort data and get data in pagination mode.
ok, DataStorePages pages = ListKeys(string prefix, bool ascending, int pagesize)
ok, DataStorePages pages = ListValues(string prefix, bool ascending, int pagesize)2
ParaDataStore allows you to get a list of keys or data sorted by key through the ListKeys or ListValue API. When calling either of the APIs, you can set the number of data items to be displayed on each page. If you enable cross-domain all_scope when creating or getting ParaDataStore, you need to add scope as the prefix in the format of scope:prefix. Certainly, any letter can be used as the prefix for prefix search.
The return value of ListKeys and ListValues is an error code and a ParaDataPages object containing pagination information. You can call ParaDataPages APIs to traverse data by page. Relevant APIs will be described in subsequent sections. The following code example gets the points ranking list of a World:
DATASTORE(
function()
print('==============Sorted Section============')
ok, store = ParaDataService.GetDataStore('PlayerInventory', 'player001')
if ok ~= 0 then
print('LoadDataStore Failed:' .. ok .. tostring(store))
return
end
for v = 1, 100 do
ok, val, info = store:SetData('item'..v, v)
end
print('==============ListKeys============')
success, value = store:ListKeys('item3', false, 5)
Log.log_page(success, value)
if success == 0 then
repeat
success, val = value:GetCurrentPage()
for k, v in pairs(val) do
print('ListKeys:' .. k .. ' : ' .. v)
end
until(not value:AdvanceToNextPage())
end
print('==============ListValues============')
success, value = store:ListValues('item3', false, 5)
Log.log_page(success, value)
if success == 0 then
repeat
success, val = value:GetCurrentPage()
for k, v in pairs(val) do
print('ListValues:', k, v)
end
until(not value:AdvanceToNextPage())
end
end)
Metadata
When you are saving data, to ensure data validity and consistency, DataStore will additionally record certain data storage operations. DataStore also allows you to record custom operation information in the form of metadata while saving data. Both types of data are stored in the form of metadata.
Operation information recorded by the system is called system metadata.
Additional information customized by users is called user metadata.
System metadata of DataStore includes:
- Data creation time
- Last update time of data
- Current data version number
All system metadata can be obtained through the third parameter in the return values of GetData, which is the ParaDataInfo instance. The specific property names are described as follows:
- createTime: Data creation time
- updateTime: Last update time
- version: Current data version number
The IncrementData, SetData, and UpdateData APIs all provide the options parameter for setting user metadata. The type of options is ParaDataSetOptions, and DataStore encapsulates metadata setting APIs through ParaDataSetOptions.
All user metadata can be obtained through the third parameter in the return values of GetData, which is the ParaDataInfo instance. The specific API is:
table GetMetadata()
The following code shows how to use GetData to get corresponding user metadata and then use the SetData to modify the obtained metadata:
DATASTORE(
function()
-- Get my_world's arena rank info
local ok, store = ParaDataService.GetDataStore('my_world', 'arena_rank_point')
if ok ~= 0 then
print('Load data store failed! code is:' .. ok .. ' message is:' .. store)
return
end
local player = CS.ParaPlayerService.GetLocalPlayer()
if player == nil then
print('Local player is nil')
return
end
local point_key = 'point_' .. player.UID
local set_options = ParaDataService.CreateDataSetOptions()
local meta = {reason = 'init'}
set_options:SetMetadata(meta)
local ok, message = store:SetData(point_key, 20, set_options)
if ok ~= 0 then
print('Load data failed: error code is:' .. ok .. ' error message is:' .. message)
return
end
local ok, point, point_info = store:GetData(point_key)
if ok ~= 0 then
print('Load data failed: error code is:' .. ok .. ' error message is:' .. point)
return
end
local meta_data = point_info:GetMetadata()
local new_meta = {}
if meta_data ~= nil then
for k, v in pairs(meta_data) do
if k == 'reason' then
print('Arena point change to ' .. point .. ' due to ' .. v)
new_meta[k] = 'change to new reason'
else
new_meta[k] = v
end
end
end
local set_options = ParaDataService.CreateDataSetOptions()
set_options:SetMetadata(new_meta)
local ok, point, point_info = store:SetData(point_key, point, set_options)
if ok ~= 0 then
print('Set data failed: error code is:' .. ok .. ' error message is:' .. point)
return
end
print('Set data succeed!')
end)
Data Version Management
Only ParaDataStore in DataStore supports version management. That is, when you use SetData, UpdateData, and IncrementData to set new data, ParaDataStore automatically stores the original data in historical versions, increments the version number of the current data by 1, and stores the latest data. Data generated 30 days ago will be automatically cleared.
You can access historical versions and their corresponding metadata or delete specified versions through version-related APIs. Deleted versions cannot be recovered. APIs related to data version management mainly include:
ok, value, DataStoreInfo info = GetVersionData(string key, int version)
ok, DataStorePages pages = ListVersionsData(string key, bool isAsce, int page_size, double min_date, double max_date)
ok, value, DataStoreInfo info = RemoveVersionData(string key, int version)
- You can use GetVersionData to access the data of a specified version. If the version exists, the value and metadata of that version will be returned as the third parameter in a form of a DataStoreInfo object. If the version does not exist, an error code and the corresponding error message will be returned.
- You can also use ListVersionsData to return all version information within a specified time period. The time period is passed in UTC format to avoid inconsistencies due to time zone differences. ListVersionsData returns sorted version information that meets the conditions in the structure of ParaDataPages. You can use the pagination access API provided by ParaDataPages to access and process version data.
- To clear the data of a previous version, you can use RemoveVersionData. After deletion, data of that version will be completely removed from the database and cannot be recovered.
The following code example shows how to traverse all version information and keep the data of the latest 5 versions:
DATASTORE(
function()
-- Get my_world's arena rank info
local ok, store = ParaDataService.GetDataStore('my_world', 'arena_rank_point')
if ok ~= 0 then
print('Load data store failed! code is:' .. ok .. ' message is:' .. store)
return
end
local player = CS.ParaPlayerService.GetLocalPlayer()
if player == nil then
print('Local player is nil')
return
end
local page_size = 10
local user_key = 'user_' .. player.UID
local ok, message = store:SetData(user_key, 100)
if ok ~= 0 then
print('Set data store failed! code is:' .. ok .. ' message is:' .. message)
return
end
local ok, message = store:SetData(user_key, 90)
if ok ~= 0 then
print('Set data store failed! code is:' .. ok .. ' message is:' .. message)
return
end
local ok, version_pages = store:ListVersionsData(user_key, false, page_size)
if ok ~= 0 then
print('Load data store failed! code is:' .. ok .. ' message is:' .. version_pages)
return
end
local kept_version_size = 5
repeat
ok, current_page = version_pages:GetCurrentPage()
if ok == 0 then
for k, v in pairs(current_page) do
print('key:' .. k .. ' value:' .. tostring(v.value) .. ' version:' .. tostring(v.version))
if kept_version_size >= 0 then
kept_version_size = kept_version_size - 1
else
store:RemoveVersionData(user_key, v.version)
end
end
else
print('GetCurrentPage return error, code is ' .. ok .. ' error message is ' .. current_page)
end
until(not version_pages:AdvanceToNextPage())
end)
Data Access Control
As DataStore occupies global server storage resources, there are certain limitations on the length of keys and values that can be stored. The specific limitations are as follows:
Key:
The key must be a string or number, and the maximum length is 256 bytes.
Value:
The value must be a string or number, and the maximum length is 32,767 bytes.
Metadata key:
The metadata key must be a string or number, and the maximum length is 256 bytes.
Metadata value:
The metadata value must be a string or number, and the maximum length is 4,096 bytes.
Size of the entire database:
The server space used for storing all data of a single World cannot exceed 1 GB.
Access traffic throttling:
The maximum number of DataStore API calls in a single room is 60 per minute. Any calls beyond this limit will be queued for processing, with a maximum of 500 requests allowed in the queue. Once the queue reaches the limit of 500 requests, all DataStore requests for that room will fail.
Error Codes
Error Code | Error Message | Remarks |
---|---|---|
-10001 | DataStore Request successful, but key not found. Ensure the data for the key is set first, then try again. | |
-10002 | Value is not number. | |
-10003 | DataStore Request successful, but version not found. | |
-10004 | Current data cannot be sorted. | |
-10005 | DataStore name can't be empty. | |
-10006 | Request failed. Please try again. | |
-10007 | Request is invalid. Invalid datastore. | |
-10008 | Request is invalid. | |
-10009 | Request is invalid. | |
-10010 | Request is invalid. | |
-10011 | Value can't be empty. If you want to save empty data, you can set the value to empty string. | |
-10012 | Key name exceeds the 256 character limit. | |
-10013 | Value size exceeds 32767 bytes limit. | |
-10014 | DataStore request dropped. Request was throttled, but throttled request queue was full. | |
-10015 | Cannot write to DataStore from Editor if Datastore is not enabled. | |
-10016 | Meta key exceed max size | |
-10017 | Metadata attribute size exceeds 4096 bytes limit. | |
-10018 | World Id is invalid. | |
-10019 | scope must be empty if all_scope is set. | |
-10020 | Key name can't be empty. | |
-10021 | param name is invalid must be string | |
-10022 | Version must be integer. | |
-10023 | param delta is invalid must be number | |
-10024 | PageSize must be within a predefined range (The maximum page size for an OrderedDataStore is 100). | |
-10026 | MaxPage and MinPage must be integers. | |
-10027 | Attribute metadata format is invalid. | |
-10028 | Cannot store this type in DataStore. | |
-10029 | Request failed. Please try again. | |
-10030 | Request failed. Please contact us. | |
-10031 | Request failed. Please try again. | |
-10032 | DataStore request dropped. Request was throttled, but throttled request queue was full. | |
-10033 | The data retrieved is empty and the number of pages in DataStorePages is 0. | |
-10034 | DataStore related functions should be placed in the DATASTORE. | |
-10035 | Unsupported value type, currently support numbers, booleans, strings and tables containing only these types. | |
-10036 | Current OrderedDataStore do not support DataStoreSetOptions. | |
-10037 | param index can not be empty and must be integer. | |
-10039 | Current DataStore do not support this operation. | |
-10040 | param Value is invalid must be number in OrderedDataStore. | |
-10041 | start_index and stop_index must be integers. | |
-10043 | param index can not be out of list range. | |
-10044 | param index can not be greater than page count. | |
-10045 | param key miss scope when enable all_scope. | |
-10046 | param is_asc must be bool. | |
-10047 | param prefix must be string. | |
-10049 | DataStore type dismatch. | |
-10050 | DataStore is not available temporarily | |
-10051 | DataStore internal error. | |
-10052 | Only world creator is allowed to access release data store. |
Updated about 1 year ago