Mismatching table passed by ModuleScript

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.

It’s Roblox bytes (In computes bytes, you do see random generated words and custom words with numbers)

data be must same ? (did you tried to unpack table with that different mismatching ?)

What???

table: 0x8a1eef13a0cdb9d5
0x8a1eef13a0cdb9d5 is an address, not a “name”

all this means is that it’s a different reference to the data

but aren’t they having same data ?

doesn’t matter if it’s the same data, the table is not the same, that’s like saying two people by the name Robert is one person

local referenceA = {someKey = 5}
local referenceB = {someKey = 5}

print(referenceA == referenceB) --// false
print(referenceA, referenceB) --// table: 0x000000000002fb58; table: 0x000000000002fb38
1 Like

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

1 Like

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

true, but even so it should pass the same reference rather than create a copy of the table…
unless there is something im missing?


Likely the issue is that it already went trough the queue bindable events by the time you created a new instance and inserted it.

This whole datastore module is a total mess, a proper datastore module should also handle data fetching,

there is completely 0 use cases for your multiple bindable events, cause you are not even destroying them or caching, they just build up

1 Like

it does clear the bindable event queue when _set_data is called, but i get what you mean, you’re right.

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

1 Like

BindableEvents will clone all arguments passed to :Fire() for serialization. It’s not particularly documented but its known behavior.

1 Like

^ so I was right, use signal module instead

1 Like

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.

damn alright, i’ll try something and come back with results, thanks!

image
except you have a strong reference to the bindable event

1 Like

seems like it works now!

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!

p.s cleaned up the code, appreciate the critique

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.