How do I convert my Datastore script to use :UpdateAsync instead of :SetAsync?

Hello!

I have a Datastore script which I am going to share later in this post. My problem is that I am not sure how to switch over to :SetAsync or when to use it and how to use it across servers (like a gifting system).

My script basically works like this:

Users can :SetAsync by invoking the server at any time (limit of 10 per minute which is the limit when users are added iirc). User data automatically saves when player leaves.

The problem is that I am really not sure how :SetAsync works when updating a specific value when my script saves as a table.

Here’s an example of what a user’s data might look like:

local defaultData = {
    ['Coins'] = 0;
    ['Inventory'] = {
        ['Accessories'] = {};
        ['Gear'] = {};
        ['Trails'] = {};
        ['Crates'] = {};
    };
    ['Passes'] = {
        ['Premium'] = false;
    };
    ['Rank'] = 'AlphaTester';
    ['Permission'] = 1;
    ['BanInfo'] = {
        ['Banned'] = false;
        ['BanReason'] = 'N/A';
        ['UnbanTime'] = 'N/A';
    };
    ['Settings'] = {
        ['UIPrimaryColour'] = {
            ['R'] = 86;
            ['G'] = 146;
            ['B'] = 127;
        };
        ['UISecondaryColour'] = {
            ['R'] = 255;
            ['G'] = 255;
            ['B'] = 255;
        };
        ['UIFont'] = 'SourceSans';
        ['GlobalShadows'] = true;
        ['PlayerMode'] = 'Visible';
        ['ResetKeybind'] = 'R';
    };
}

When converted to instances:

Function to save, it reconstructs the player’s data from instances to a table:

local function saveData(player)
    local key = player.UserId
    local playerFolder = player:FindFirstChild('PlayerData')
    
    local odsFolder = player:FindFirstChild('ODSData')
    
    local obbiesData, winsData = odsFolder:FindFirstChild('Obbies').Value, odsFolder:FindFirstChild('Wins').Value

    function convertInstanceToData(currentFolder)
        local returnedData = {}
        for i,v in pairs(currentFolder:GetChildren()) do
            if v:IsA('Folder') then
                returnedData[v.Name] = convertInstanceToData(v)
            else
                returnedData[v.Name] = v.Value
            end
        end
        return returnedData
    end

    local constructedTable = convertInstanceToData(playerFolder)

    local successful, errorMessage = pcall(function()
        dataStore:SetAsync(key, constructedTable)
        winsDataStore:SetAsync(key, winsData)
        obbiesDataStore:SetAsync(key, winsData)
    end)

    if not successful then
        warn(tostring(errorMessage))
    end
end

TIA for any help!

Oh also little PS, winsDataStore and obbiesDataStore are in different datastores as they are ordereddatastores, not standard ones.

1 Like

I once had this exact same question and unfortunately, I wasn’t able to get any help whatsoever, to prevent this, hopefully this post will be helpful to many others who come across the same question.


First of all, let’s take a look at how you usually save data;

local successful, errorMessage = pcall(function()
        dataStore:SetAsync(key, constructedTable)
        winsDataStore:SetAsync(key, winsData)
        obbiesDataStore:SetAsync(key, winsData)
    end)

This would be your SetAsync way to save data, so how do we change to :UpdateAsync? Well, here’s how it works. Let’s say I wanted to UpdateAsync to your dataStore. This is how it’d be done:

dataStore:UpdateAsync(key, function(OldData)
-- So basically, you can execute code here before saving happens, you get access to the OldData that the key previously had, for example, if the key had a value of 5 saved to it, OldData would be equal to 5. This allows for checks such as for session locking and checking OldData
    return constructedTable -- by returning constructedTable, it get's saved
end)

It is also possible to cancel UpdateAsync which you can do by:

dataStore:UpdateAsync(key, function(OldData)
-- So basically, you can execute code here before saving happens, you get access to the OldData that the key previously had, for example, if the key had a value of 5 saved to it, OldData would be equal to 5. This allows for checks such as for session locking and checking OldData
    return nil -- by returning nil, you're not actually saving nil, you're canceling this request, to remove data consider RemoveAsync
end)

So, what would it look like in your code? If you couldn’t tell from the example change:

    local successful, errorMessage = pcall(function()
        dataStore:SetAsync(key, constructedTable)
        winsDataStore:SetAsync(key, winsData)
        obbiesDataStore:SetAsync(key, winsData)
    end)

to:

    local successful, errorMessage = pcall(function()
        dataStore:UpdateAsync(key, function(OldData)
            return constructedTable
        end)
        winsDataStore:UpdateAsync(key, function(OldData)
            return winsData
        end)
        obbiesDataStore:UpdateAsync(key, function(OldData)
            return winsData
        end)
    end)

Hopefully this helped you understand how to use UpdateAsync, from what I’ve seen, I don’t believe you need any changes since you don’t seem to be in need of OldData but maybe you will in the future.


PS:

I’ve just read this part, what you’re going to need to do is check if the Player is online and in a game, if so, you’re going to need MessagingService for cross server communication and edit their data live, if they are offline then you can utilize UpdateAsync to update the old data like so:

ExampleDatastore:UpdateAsync(Key, function(OldData)
    OldData.Gold = OldData.Gold + 10 --add's 10 gold to their current data before this save
    return OldData
end)
7 Likes

Thanks for that!

My biggest issue though is cross-server data modification such as banning when a player is online and in-game. If I am in a different server than the player and I set their BanInfo.Banned value to true, and then the user exits, their BanInfo.Banned value will get set back to false. Is this something :UpdateAsync could help with, or would MessagingService be more optimal for this?

Another thing, is OldData automatically assigned a value from what is already saved from the server? So it’s kind of like :GetAsync()?

Last thing, inside of each UpdateAsync function, is the returned value going to be what is saved? So it’s similar to a pcall?

Edit: Ah okay, your edit answers my first and third question. Still a little confused about OldData though.

MessagingService would be optimal here, though, one method could be that when a player joins a game, you can add some sort of “tag” to indicate that they are in game liked InGame = true, and when they leave set InGame = false. You can then read this in GetAsync or UpdateAsync and if they are InGame you’d use MessagingService.


OldData is basically GetAsync’d data from what is currently saved, think of UpdateAsync like GetAsync + SetAsync combined into one.


what’s returned inside the function is saved (so normally the second argument of SetAsync you want to return that), take a look at my edit in my first post, I edit the Gold data before returning it adding 10 Gold.


1 Like

Ah okay, that answers all of my questions. Perfect! Thank you.

I’m going to keep your initial response as the solution though as it’s probably what’s most relevant if people find themselves reading this topic.