I’m currently making a DataStore module script for my friend’s and my game. For some reason, when I get a stored datastore table via DataStore:GetAsync() and put it in the module, any other script using a function to get that table will receive a different copy each time it’s called.
Datastore server Script
-- Load player data upon joining.
PLAYERS.PlayerAdded:Connect(function(player: Player)
-- Get data
local success, player_data_response = pcall(function()
return data_store:GetAsync(tostring(player.UserId))
end)
-- Load data
if not success then -- An internal error has occured.
data_store_module._set_data(player, nil, "failed")
else -- Data successfully received!
data_store_module._set_data(player, player_data_response, "success")
print(tostring(data_store_module.queue_get_data(player))) -- table: 0x8a1eef13a0cdb9d5
end
end)
Datastore module
local function FormatData(data: any?): data_format
if not data then
return {
saved_points = 0
}
end
return data -- in this specific case, no changes were made to the argument table, therefore it will return the same thing that was passed.
end
-- Create a new data dictionary for the player.
data_store_module._set_data = function(player: Player, data: data_format?, state: string)
local formatted_data = FormatData(data)
print(tostring(formatted_data)) -- table: 0x8a1eef13a0cdb9d5
player_data_dictionary[player] = formatted_data
player_data_states[player] = state
if data_queue[player] then
for i, bindable_event in data_queue[player] do
bindable_event:Fire(formatted_data, state)
end
data_queue[player] = nil
end
end
-- Yields the current thread and returns the player's data and it's loading state until it becomes available.
data_store_module.queue_get_data = function(player: Player): (data_format, string)
local player_data = player_data_dictionary[player]
if not player_data then
local bindable_event = Instance.new("BindableEvent")
if not data_queue[player] then
data_queue[player] = {}
end
table.insert(data_queue[player], bindable_event)
return bindable_event.Event:Wait()
end
return player_data, player_data_states[player]
end
Anonymous server script
PLAYERS.PlayerAdded:Connect(function(player: Player)
local data, state = data_store_server_module.queue_get_data(player)
print(tostring(data)) -- table: 0xe6e9eb85705db415
end)
Output:
table: 0x8a1eef13a0cdb9d5 -- Datastore module
table: 0xe6e9eb85705db415 -- Anonymous server script
table: 0x8a1eef13a0cdb9d5 -- Datastore server script
TL;DR:
When you call a function from a module that is supposed to return a shared table, it returns a copy of that table instead.
correct, my issue is with the table references.
when i attempt to grab a player data table with queue_get_data, it returns a different reference with exactly the same data, which is weird because i store and return the same exact table i get from the datastore service in the player_data_dictionary table.
That’s because your anonymous server script runs at the same time your Datastore server Script runs, since the GetAsync is asynchronous, it takes time, and the data hasn’t been initialized, so your queue data is this
also you are creating multiple bindable events for no reason, perhaps the bindable event returns a copy, consider using a signal module since its faster
thing is, when a script calls queue_get_data, it will yield the script until there is data.
the datastore server script will call _set_data on the module when it finishes receiving a player’s data from the datastore service, which will fire all the queued bindable events and resume every script waiting for the data.
anonymous script > queue data request > wait until bind event
datamodule server script > receive data > set data in module and fire all queued binds
anonymous script > bind received > resume thread
it clears references to the bindable events, the bindable events still exist within your game and memory, you need to destroy it and the set the reference to nil
doesn’t roblox’s garbage collection system take care of that? i’ve heard that once an instance is not mentioned anywhere else, it should be cleared from memory.
local function ReleaseDataQueueYield(player: Player, state: string)
if data_queue[player] then
data_queue[player]:Fire()
data_queue[player] = nil
end
end
-- Create a new data dictionary for the player.
data_store_module._set_data = function(player: Player, data: data_format?, state: string)
player_data_dictionary[player] = FormatData(data)
player_data_states[player] = state
ReleaseDataQueueYield(player, state)
end
-- Remove a player's data dictionary.
data_store_module._remove = function(player: Player)
player_data_dictionary[player] = nil
player_data_states[player] = nil
ReleaseDataQueueYield(player, "removed")
end
-- Yields the current thread and returns the player's data and it's loading state until it becomes available.
data_store_module.queue_get_data = function(player: Player): (data_format, string)
local player_data = player_data_dictionary[player]
if player_data then
return player_data, player_data_states[player]
else
if not data_queue[player] then
data_queue[player] = Instance.new("BindableEvent")
end
local state = data_queue[player].Event:Wait()
return player_data_dictionary[player], state
end
end
i didn’t know bindable events clone tables, i assumed it should work as i expected because it’s not the same case for instances.
@ChiDj123, i took your advice and unified the bindable events into a single one instead of creating multiple instances.
i’ll check out signal module, thanks for the help!