function AddCoins()
local oldAmount = ts.Economy.MetaStorage.GetStorageAmount(1010017) -- Get amount of coins
print(oldAmount)
ts.Economy.MetaStorage.AddAmount(1010017, 50000) -- Add 50000 coins
local newAmount = ts.Economy.MetaStorage.GetStorageAmount(1010017) -- Get amount of coins
print(newAmount)
end
Now, judging by the codesnipped above. If before running AddCoins()
snipped you had a total of 10000 coins, you would probably not expect the output to be:
10000
10000
But that is exactly what you are gonna get.
Yes, you read correctly, this will be the output, even though we just added 50k coins to our profile BEFORE getting newAmount. Why is this the case?
Some (not all) Write Actions are executed on the next game tick.
Tick | Actions |
---|---|
0 | Command to Add Money is run |
1 | Money was added to metastorage |
First and foremost: functions are by default executed in a blocking manner on the main thread. That means, The Game tick wants to run a function, the entire function is executed, only then the next game tick happens. (That is also why, when you code an infinite loop in a script, it will just block the entire game forever)
Tick | Actions |
---|---|
1 | Running Function: Run Test Get Amount of Coins Add 50k Coins Get Amount of Coins |
2 | Money was added to Metastorage |
By now, you should spot the problem.
On the same tick, we are getting coins amount, adding 50k coins, then getting coins amount again, BEFORE the Metastorage has updated. That means, because the writeback happens only on the next game tick, we are getting the Storage at still the current tick's state.
So, obviously we need a way to fix this, right? The solution is to let the next game tick happen in between Write and Read.
That happens through lua coroutines, and luckily, we don’t need to set up the coroutines ourselves (for the most part) as Ubisoft has already given us the tool we need.
You can run a function as a new coroutine using system.start(func)
, passing in a function. It will then run that function until it returns as a coroutine - and when the coroutine is dead, it gets dropped.
Most importantly though, upon each yield, the game advances to the next tick and only then resumes the coroutine.
You can also use
system.start(func, label)
to name your coroutine. You can see currently active coroutines insystem.internal.coroutines
If you want to wait for some time instead of just one tick, you can use
system.waitForGameTimeDelta(ms)
So, with everything we know, we adapt our code, add a yield after the write action, and run our function as a managed coroutine.
function AddCoins()
system.start(function ()
-- Get amount of coins
local oldAmount = ts.Economy.MetaStorage.GetStorageAmount(1010017)
print(oldAmount)
-- Add 50000 coins
ts.Economy.MetaStorage.AddAmount(1010017, 50000)
coroutine.yield()
-- Get amount of coins
local newAmount = ts.Economy.MetaStorage.GetStorageAmount(1010017)
print(newAmount)
end)
end
Which will give us our desired flow of events:
Tick | Actions |
---|---|
1 | Start Coroutine: Run Test Get Amount of Coins Add 50k Coins |
2 | Money was added to Metastorage Get Amount of Coins |
function AddCoins(amount)
system.start(function ()
-- Get amount of coins
local oldAmount = ts.Economy.MetaStorage.GetStorageAmount(1010017)
print(oldAmount)
-- Add 50000 coins
ts.Economy.MetaStorage.AddAmount(1010017, amount)
coroutine.yield()
-- Get amount of coins
local newAmount = ts.Economy.MetaStorage.GetStorageAmount(1010017)
print(newAmount)
end)
end