Memory Store Service Tutorial
Introduction
Today, I'll be showing you how to use Memory store service. This service allows for easy cross server communication. With low latency, almost like datastores. It makes stuff like queues easier. This data is not persistent with a hardcoded expiration date. Data will also stay, even when all servers of a game shut down. Data will only be removed after the expiration time is met. For this tutorial, i'll show you how to use Queues, and , let's get right into it!Queues
Explanation
First, let's go over about queues. Queues allow you to send data across servers in a queue like system. Each item has a priority. The higher the priority, the higher up the queue the data will be. Data here can be read using the `ReadAsync()` function.Scripting
Setting up
For this example, we'll be scripting a cross-server notification anyone clicks a button.First, we need to make our button. To do that, create a new part
Next, hover over the part in explorer(view â explorer) and click the + and insert a click detector
Once you make the button, insert a server script inside the button. Woo hoo!
coding
First, letâs set up our Queue. To do this, we can use the :GetQueue()
function. This function will create/get the specified queue name.
local memoryStoreService = game:GetService("MemoryStoreService")
local clicksQueue = memoryStoreService:GetQueue("Clicks") -- "clicks" is the queue name
After we set up the queue, lets set up our variables.
local memoryStoreService = game:GetService("MemoryStoreService")
local clicksQueue = memoryStoreService:GetQueue("Clicks") -- "clicks" is the queue name
local btn = script.Parent -- button
local detector = btn:WaitForChild("ClickDetector") -- click detector
local function logClick(plr:Player) -- function which will log clicks
end
detector.MouseClick:Connect(logClick) -- connect our function.
Now that weâve set up the variables, weâre ready to script the actual stuff!
In order to add data to a queue, you use 1 function, that is :AddAsync()
function. This takes 3 arguments. value
, the queue itemâs value, expiration date, which is how long the data will be there in the queue(this is in seconds.), and finally the priority, which is the priority of the item in the queue.
So letâs add some data to our queue. To do this, we can use the following code:
KEEP IN MIND, ANYTHING YOU DO IN MEMORYSTORESERVICE SHOULD BE BACKED WITH A PCALL, AS THE REQUEST COULD FAIL!
local memoryStoreService = game:GetService("MemoryStoreService")
local clicksQueue = memoryStoreService:GetQueue("Clicks") -- "clicks" is the queue name
local btn = script.Parent -- button
local detector = btn:WaitForChild("ClickDetector") -- click detector
local function logClick(plr:Player) -- function which will log clicks
local success, err = pcall(function()
local value = string.format("%s clicked the BUTTTON", plr.Name) -- add item to queue
clicksQueue:AddAsync(value, 0.5, 0)
end)
end
detector.MouseClick:Connect(logClick) -- connect our function.
Code explanation:
Code explanation
the value is the value that is added to the queue. This can be a table, string anything. As long as itâs a UTF character. For info on how to save non UTF characters you can check out this post made by the coolest forummer, @regexman.
The 2nd arg(0.5) is how long the data will be in the queue. For this example, this data will be in the queue for half a second. The max for this value is 30 days(aka: 2,592,000 seconds.)
Finally, the 3rd arg(0) is the priority. For this example, I set it to 0. You can make it any number you want.
Next, weâll need a way to read this data. We can do this via the :ReadAsync()
function. This will return a table with our queue data.
As of writing this, there is no event for detected when data is added to queues. The only way right now is via a while loop. Here is the code bellow. If you dont understand something, read the code explanation OR the comment on that line.
local memoryStoreService = game:GetService("MemoryStoreService")
local runService = game:GetService("RunService")
local clicksQueue = memoryStoreService:GetQueue("Clicks") -- "clicks" is the queue name
local btn = script.Parent -- button
local detector = btn:WaitForChild("ClickDetector") -- click detector
local function readQueueData()
local success, queueItems = pcall(function() -- wrap in pcall to prevent app from crashing
return clicksQueue:ReadAsync(20, false, 0) -- read 20 queue items.
end)
if success and typeof(queueItems) == "table" then-- check if request succeeded and if the data is a table
for _, data in ipairs(queueItems) do
print(data)
end
elseif typeof(queueItems) == "string" then -- check if the queue items is a string. If it is, it'll indicate that it is an error.
warn("failed to fetch queue data for reason of: %s", queueItems)
end
end
local function logClick(plr:Player) -- function which will log clicks
local success, err = pcall(function()
local value = string.format("%s clicked the BUTTTON", plr.Name) -- the value of the queue item
clicksQueue:AddAsync(value, 0.5, 0)
end)
if not success then
warn("something went wrong while adding item to queue. For reason of: %s", err)
end
end
detector.MouseClick:Connect(logClick) -- connect our function.
while true do
readQueueData()
task.wait(0.1)
end
Code explanation:
Code explanation
First, we wrap the ReadAsync request in a pcall
. Which prevents our code from crashing if the request fails. In that same pcall
, weâll return the first 20 items in the queue. Keep in mind, the max for reading a queue in one request is 100.
The first arg(20) is the amount of itemâs weâd like to get.
The 2nd arg(false) is an AllOrNothing arg. This controls how the queue returns items. If the arg is true and the queue has less items than the read amount(first arg), nothing will return. It has to have more or the same amount as the read amount.
With false, itâll return data even if the queue has less items than the read amount.
Next, weâll check if the code succeeded and if the queueItems is a table.
Next, weâll loop through the queueItems and print the data.
The elseif is to check if the queue items is a string. This indicates that an error occurred. The reason why we check this is because the queue may return nil, so the elseif would fire resulting in the warn falsely firing.
Now we can test our code in studio and see if it works!
Once you click the button you should see '[USERNAME] clicked the BUTTON!" In the output(view â output)
testing
now that weâve checked that our code works in studio. Letâs see if the code is truly âcross server.â To test this, we can go into multiple accounts and play our game to test. KEEP IN MIND, YOUR STUDIO TESTING WILL NOT APPEAR IN REAL SERVERS! SO TEST IN THE SITE AND NOT STUDIO!!
Once you click the button on your alt. Go back to your main and press F9(or type â/consoleâ in the chat) and click on âServer.â
Once you do, you should see ur alts clicks on ur mains server
If you see something like this, you did it correctly! If not, then repeat the last few steps.
Congratz! You just created a cross-server button logger using Memory Store Service!
Hereâs a cake to celebrate!
Sorted Maps
Explanation
I'll start off with explaining sorted maps.Sorted maps allows for easy cross-server communication in a datastore like structure. With keys and values. Sorted maps have all the functions of a datastore. Which is good for data thatâs meant to stay for a small amount of time. If you know how to use datastores, you should have little to no learning curve! Without further ado, letâs go right into it!
coding
For this example, Iâll be showing you how to make a cross-server Player join logger. This wont require any setting up as we can do this with 1 script.
First, letâs define our variables.
local players = game:GetService("Players")
local memoryStoreService = game:GetService("MemoryStoreService")
local joinsMap = memoryStoreService:GetSortedMap("Joins") -- creates/gets a sorted map.
local joinMsg = "%s has joined the game!" -- the message that we'll display to the output
local function logJoin(plr:Player)
end
players.PlayerAdded:Connect(logJoin) -- connet function
Now that weâve done that, weâll need to add data to our Map. To do this, we can use the SetAsync()
function. This function takes in the following: key which is how we will access the data. Value which is the value associated with the key. The last arg is the expiration. This is how long the data will exist in seconds. After the expiration is met, the data will be erased from the Map. Now letâs start scripting this!
local players = game:GetService("Players")
local memoryStoreService = game:GetService("MemoryStoreService")
local joinsMap = memoryStoreService:GetSortedMap("Joins") -- creates/gets a sorted map.
local joinMsg = "%s has joined the game!" -- the message that we'll display to the output
local function logJoin(plr:Player)
joinsMap:SetAsync(plr.UserId, string.format(joinMsg, plr.Name), 0.5) -- add data to map
print(joinsMap:GetAsync(plr.UserId)) -- get data
end
players.PlayerAdded:Connect(logJoin) -- connet function
Code Explanation:
Code Explanation
First, we need to add data to Map. To do this, we can use the :SetAsync()
function.
The first argument is the key. Which in this case is the playerâs userid.
The second argument is the value. Which will be the join message("[PLAYERNAME] has joined the game!"). You can set the value to numbers, strings, and tables.
Finally, the 3rd argument is the expiration. Since this data doesnât need to be persisted for long, I set the expiration as half a second.
Next, we need to read this data. To do so, we can use the :GetAsync()
function. Which takes 1 argument, the key. Which in this case is the playerâs userid.
Now that we have our code, we can test.
Once you click play, you should see â[USERNAME HERE] has joined the game!â
Now thereâs an issue with this code, itâll specifically get the data from that 1 key when a new player joins. Which means itâs not really cross server. To fix this, we can read data using the GetRangeAsync()
function. Which takes 3 args Sort Direction, which is an enum value. If the value is Descending
itâll get data from oldest to newest. If itâs Ascending
itâll get data from newest to oldest. The next argument is the amount of data youâd like to get. This will be influenced with the first arg. The least you can fetch is 1 and the most you can fetch is 200.
We will need to wrap this inside of a while loop. Because as of writing this, there is no event to detect when new data is added.
Now that we understand the args, we can write our code now! If you dont understand something read the comment on that line, OR ask below in the comments.
local players = game:GetService("Players")
local memoryStoreService = game:GetService("MemoryStoreService")
local joinsMap = memoryStoreService:GetSortedMap("Joins") -- creates/gets a sorted map.
local joinMsg = "%s has joined the game!" -- the message that we'll display to the output
local function fetchAllData()
local success, data = pcall(function()
return joinsMap:GetRangeAsync(Enum.SortDirection.Ascending, 200) -- return the data
end)
if success and typeof(data) == "table" then -- check to see if code succeeded and if data is a table
for _, join in ipairs(data) do
local key = join.key
local value = join.value -- the data's value
joinsMap:RemoveAsync(key) -- remove the data from the map
end
elseif typeof(data) == "string" then -- check to see if data is a string. If it is it means that an error happened
warn(string.format('failed to fetch JoinMaps data for reason of: %s', data)) -- log error
end
end
local function logJoin(plr:Player)
local success, err = pcall(function()
joinsMap:SetAsync(plr.UserId, string.format(joinMsg, plr.Name), 0.5) -- add data to map
end)
if not success then warn(string.format("failed to set %.f's data for reason of: %s", plr.UserId, err)) end -- log error
end
players.PlayerAdded:Connect(logJoin) -- connet function
while true do
fetchAllData()
task.wait(0.1) -- IMPORTANT if you dont add this your code wont work!
end
Next, letâs test if this code is truly âcross-server.â
To do this, log into an alt acc and play ur game there.
Once you do go to the console and u should see ur alts name there in the console.
Outro
Thank you so much for reading! I hope this helped you ! If you have any questions, feedback, or want to improve anything I said, feel free to say it below! PEACE!