Hey! ;D
I want to make something similar to a global leaderboard I guess using ordered datas stores, but I need it to reset daily. How would I go about that?
(Also could be cool if I could have different variants of ir one of which is resetting daily and one which is resetting weekly)
Thanks in advance,
Mun :Р
That sounds like a really big hastle, especially considering rate limits, so I’d recommend just changing the store to a new one whenever you want to reset. It’ll have effectively the same result.
As @ChipioIndustries was alluding to, I would create a system that uses a new datastore every day. The way I would do this is by creating a datastore that saves the last time that the data was reset, and then having your ODS key defined by a variable.
This is some example code on how exactly that could be done, note that I wrote this pretty quickly so I apologize for any errors:
DSS = game:GetService("DataStoreService")
TDS = DSS:GetDataStore("LastReset")
spawn(function()
while true do
local LastReset = TDS:GetAsync("LastReset")
local TimesReset = TDS:GetAsync("TimesReset")
if LastReset then
local Time = os.time() - LastReset
local Hours = Time/3600
if Hours >= 24 then
TDS:SetAsync("LastReset",os.time())
TDS:SetAsync("TimesReset", TimesReset + 1)
ODS = DSS:GetOrderedDataStore("DataStore"..TimesReset + 1)
else
ODS = DSS:GetOrderedDataStore("DataStore"..TimesReset)
end
else
ODS = DSS:GetOrderedDataStore("DataStore"..1)
TDS:SetAsync("TimesReset", 1)
TDS:SetAsync("LastReset",os.time())
end
wait(300)
end
end)
That’s… a little overcomplicated for a simple daily-based DataStore, no? You could just form a key off of yday and year from os.date. It would get rid of the need for two DataStore requests per iteration (despite the long interval, I know), it’s straightforward and requires little to no active maintenance on your end.
local currentDate = os.date("!*t")
local identifierKey = ("%i%i"):format(currentDate.yday, currentDate.year)
local dataStoreName = ("DataStore%s"):format(identifierKey)
local DailyOrderedStore = DataStoreService:GetOrderedDataStore(dataStoreName)
Year is used to prevent conflictions in other years for the day of the year (1-365, LY +1).
What if I need serveral of such things then? (For example one resetting weekly, and one daily, and maybe seasonly, but they all store the same values) Would I use more datastores? How could I avoid throttling the queue while making all the values show up for players?
You would need to open three DataStores to do this and you won’t be able to dodge throttling if it occurs. You can sacrifice the need for several time period stores (what’s your use case for having that many?) and just keep one or two.
If you’re interested anyway, use the same method I proposed but change the formation of the key. For a daily key, use the day of the year as well as the year to prevent other year conflictions. If you need a weekly reset, use the week of the year. I don’t know what you define as a season (a literal season lasting 4 months or one of those game seasons), so that’s up to you to figure out.
What I was thinking is that what happens if the server is up for more than 24 hours? I am not sure if that is possible, but I was thinking I would have to iterate in order to prevent an issue like that from happening. Otherwise I would have just created a system similar to yours, although admittedly not as clean.
Had to do this recently for a weekly donation leaderboard system, to my knowledge, you can’t clear a datastore unless you know all of the keys, and when you’re using the UserId as a key, you don’t.
Used the method described by @colbert2677
A valid question. Generally I’ve never known servers to have an uptime of over 24 hours but it is possible and that’s why it can’t be ruled out.
Since formation of the key and fetching a DataStore (GetDataStore, GetGlobalDataStore and GetOrderedDataStore) operate at no cost nor budget, you can refresh this in a background loop or before you make any DataStore calls. You could also set up a separate function that returns the correct key and DataStore. Arguably not clean but as clean as it can get.
local function getDayStore()
local currentDate = os.date("!*t")
local identifierKey = ("%i%i"):format(currentDate.yday, currentDate.year)
local dataStoreName = ("DataStore%s"):format(identifierKey)
return DataStoreService:GetOrderedDataStore(dataStoreName)
end
local orderedDataStore = getDayStore()
Is it generally a good practice to use a lot of different datastores though, if I will have a game that gets a new datastore every day? Wouldn’t there be excess data in the memory? Am I okay with using that permanently? Wouldn’t it be bad on roblox’s side?
What other choice do you have? The only other options for accomplishing this are even worse off:
-
Attaching a daily unique identifier to a key when performing a GetAsync which will overinflate the amount of keys in your DataStore (be lucky Roblox handles this for you in the backend and that you have to do little to no internal data management).
-
Hacking something together to reset data of players per day, which will cause terrible consistency issues and use more budget than should be necessary.
If it’s just for a scoreboard, no it’s not bad practice. It’ll be fine and your memory won’t be affected either. You can is virtually infinite DataStores in the first place. What would be bad is to use multiple DataStores in cases where you can consolidate, which is extraneous to this topic.
Roblox dishes out a large budget for data storage. I asked about this some time ago, you may be interested in the discussion. See this thread:
https://devforum.roblox.com/t/how-does-roblox-support-our-datastore-usage/334189
Personally the way I would do this is by using a secondary datastore for metadata. This datastore says when keys were last updated and if the keys are out of date simply ignore them.
Perhaps datastore scopes would work better here. The scope could contain the time stamp rather than the datastore name.
Depends on your case and structure. Scope and name are simply used in tandem for partitioning, so placement in either field will yield the same result regardless. It may be more conventionally sound to use scope but using either or is not necessarily better when they serve the same function, which is to differentiate and create uniqueness.
Levels_0000, global
Levels, 0000
0000, Levels
etc.
The difference is how they’re opened in the backend which is invisible to all except the developer and Roblox.
With name:
OrderedDataStore_119
OrderedDataStore_219
With scope:
OrderedDataStore
119
219
In case of having different kinds of valid data time, for example if I had serveral datastores that store the same playerdata, but one resets daily, and other resets, for example, weekly, could I just use completely different datastores and just have twice as much ordered datastore request calls (which I’m fine with) and be cool with that, or would I have to combine and reuse them in ways to optimize efficiency and data amount?
You would use the same concept for any time period, the only thing that changes is your unique identifier which is retrieved from os.date. Each time period should work in year-time: day of the year, week of the year, month of the year. You’ll also have to add an extra identifier to prevent conflictions: I’ll point this out later.
In terms of working with an OrderedDataStore, you would have to create completely new ones to establish them being unique time periods. It’s in this circumstance where I would recommend, as @Hexcede pointed out above, a DataStore scope to make your life easier. You will experience more expense depending on time periods and amount of leaderboards (n*x) so apply that wisely. You’ll notice games with global leaderboards tend to have only 1-3 leaderboards, scarcely more if they’re feeling like pushing the limits with a touch of risk.
If you want one daily and one weekly, you simply need to find a way to calculate what week of the year it is. This is slightly more difficult to get but it is manageable: examples exist around the DevForum as weekly leaderboards have been asked of before.
tl;dr Use the same DataStore name but change the scope to make life easy and maintenance just as easy and good for organisation, so on.
Examples:
-- 344th day of 2019
GetOrderedDataStore("Levels", "D3442019")
-- 37th week of 2019
GetOrderedDataStore("Levels", "W372019")
Notice in the examples above I prefixed the daily store with D and the weekly with W? Not only does this further help you identify what scope is acting for what time period, but it prevents conflictions as well.
For example: if you did not have that letter identifier, the 34th day of 2019 (342019) would conflict with the 34th week of 2019 (342019). Although it’s not possible for the 34th day of 2019 to be in the 34th week, the 34th week would pull results from the 34th day DataStore, which you don’t want.
Let me know if you don’t follow or have any further questions.