How to use DataStore2 - Data Store caching and data loss prevention

The code they provided won’t work, DataStore2 never returns nil and if it did that code would set the variable to 0, not to a store.

Use the second argument of Increment if you can’t guarantee the data will exist by then.

MassStore:Increment(1, 0)
1 Like

Thanks for correcting me. Not to argue, but would it be more valid to do MassStore:Set(0) so that it is still a DataStore2?

Also @sebi210:

Curious, when specifically are you printing and getting a nil value, since DataStore never return nil?

:Get() can return nil. DataStore2() can not.

Yes.

So, I’ve been kind of against using DataStore2 because of the fact I feel like it’s somewhat inefficient. I put together something, and I was curious If this could be a fix for that.

local DataStore2 = require(...)
local Players = game:GetService("Players")
local MarketPlaceService = game:GetService("MarketPlaceService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Data = {
    Coins = 0,
    Inventory = {},
    XP = 0,
    Level = 1
}

function recurseTable(tbl, func) -- just a for loop, originally used as a means of easily recursing nested tables but changed it because i have no use for that
    for index, value in next, tbl do 
        --if typeof(value) == 'table' then -- realised i don’t need this, typing this quickly.
            --recurseTable(value, func)
        --else
            func(index, value)
        --end
    end
end

local DataList = {}
recurseTable(Data, function(index, value)
    table.insert(DataList, index)
end)

DataStore2.Combine("DATA", unpack(DataList))

local function playerAdded(player)
    recurseTable(Data, function(index, value)
        local DataThingy = DataStore2(player, index)

        local function callRemote(value)
            ReplicatedStorage.Events:FindFirstChild(index):FireClient(player, value)
        end

        callRemote(DataThingy:Get(value))
        DataThingy:OnUpdate(callRemote)
    end)
end

Players.PlayerAdded:Connect(playerAdded)

for _, player in next, Players:GetPlayers() do
    playerAdded(player)
end

-- marketplace example
local CashProducts = {}
CashProducts[123123] = 100
function MarketPlaceService.ProcessReceipt(receipt)
    local playerId = receipt.PlayerId
    local player = Players:GetPlayerByUserId(playerId)
    if CashProducts[receipt.ProductId] then
        local coins = DataStore2('Coins', player)
        coins:Increment(CashProducts[receipt.ProductId])
        return Enum.ProductPurchaseDecision.PurchaseGranted
    end
end

ReplicatedStorage.BuyProduct.OnServerEvent:connect(function(player, productName)
    if not Products[productName] then -- product doesn't exist
         return -- end it here
    end -- goo goo gaga
    local coinStore = DataStore2("Coins", player)
    local productPrice = Products[productName].Price

    if coinStore:Get(Data.Coins) >= productPrice then
        print("Buying product", productName)
        coinStore:Increment(-productPrice)
    end
end)

Yeah it’s a little messy, but I’m just giving a general idea of what I’d do. Obviously I’d use, OOP and stuff but I feel like this could be a fix.

1 Like

What exactly is this fixing? This just looks like mostly normal DataStore2 usage to me.

2 Likes

The fact that DataStore2 is inefficient.

Where is DataStore2 inefficient that your code “fixes”?

1 Like

I must’ve misunderstood how datastore2 actually works, So it doesn’t fix anything, it just is a way of using datastore2.

The way I want to save and update a table is by using :GetTable() → modify table → Set()

Is it possible that when this is done by 2 scripts simultaneously, the data from one of the scripts won’t be saved because the other script overwrites the data? If so, are there any alternatives to save a table simultaneously without risking data-loss?

example picking up Gold and Silver from the ground at exactly the same time

script1: (gives gold)
GetTable() → tablename[gold] = tablename[gold] + 1

script2: (gives silver)
GetTable() → tablename[silver] = tablename[silver] + 1

script1:
:Set(tablename)

script2:
:Set(tablename)

does this example means that the data which script 1 is trying to update, is not being saved? since script2 is overwriting the data with his own data, which did not include the data script 1 was about to :Set() because script 2 used GetTable() before script1 Set() its data?

Roblox is single threaded, so you won’t have real multithreading issues. I’ve never had this issue and I regularly use several scripts handling data. You could have your own issues though if you have yields.

Take for instance, this pseudo code:

local currenciesStore = DataStore2("Currencies", player)
local currencies = currenciesStore:GetTable({ Gold = 0, Silver = 0 })

-- do some number crunching to determine currencies

currenciesStore:Set(currencies)

If our crunching doesn’t yield, then here’s the process:

  • Get the data in the store
  • Do some number crunching on it (like giving them free gold or something)
  • Set the data

This is so far so good. But if our crunching yields, everything breaks!

  • Get the data in the store
  • Crunch numbers on it
  • Yield, let other scripts have a chance to run
  • Those other scripts might set their own data to the data store, but we don’t know it!
  • We override the data with the old data!

Be wary and keep this in mind.

1 Like

I think I’m missing some basic understanding about what single threaded means.
Doesn’t roblox run multiple threads at once? like multiple while loops? If so, why won’t script2 get a chance to run if script1 is not yielding? Is the interval between :get and :set just too small for script2 to use old data? Or does the thread “pause” whenever it is yielding giving script2 a chance to run it’s thread untill the yielding (pause) is over?

Since :Get() is called when a player is joining the game, it will not yield because script1 and script2 will be ran while the player is playing the game, and since Set() never yields, yielding won’t be a problem for me.

1 Like

No. Roblox uses green threading, meaning that your code looks like it is using multiple threads but it’s actually just very intelligently swapping whenever you yield.

:Get() (after the first time) and :Set() never yield, so the threads aren’t switched while it’s still running.

2 Likes

Okay, I edited my previous post a bit too late I see,

I added the the question whether yielding “pauses” the thread, giving the other script a chance to run while it is paused. You gave me the answer anyway!

Thank you.

1 Like

Can I use global data stores for this?

Not sure what you mean by this? Do you mean with non-player keys? If so, no.

1 Like

If I save data in place1 then teleport the player into a branch-place can I still get the same saved data from place1?

Yes, DataStore2 works fine and keeps data between places of the same game.

2 Likes

I am planning on using this instead of the default datastore in my game, i am saving with tables.
if and how would this be possible? would i do it the same as if i was doing a normal datastore

tables are the only way i can save as my data needs large amounts of info such as level, xp, skins etc.

if i can’t do this, how would i do it?

Please read the thread.

3 Likes

Ok, so… i roughly understand how it’s supposed to work, but what i’m wondering is what does this imply? How does this affect throttling?
How do i go about setting the data? Do i still have to do ~ 3 min. intervals between saving, and if so, does it mean both of them, or just one?
My intuition says that i shouldn’t care about the regular data store causing trouble, if the keys are different, i’m not overwriting then, but i should have the ordered data store at intervals, is this true?

1 Like