Trouble with a Module

Hello!

I am currently working on making a simple Datastore module. I was attempting to create a system through which a localscript could call the “Save” function, and their data would be sent through a RemoteFunction which would invoke the same module on the server side so it could save the data.

code:

function Datastore.Save(key, dataName, data)
	if key and dataName and data then
		if RS:IsServer() then
			print("Save is running on the server")
			local cachedData = cache[key]
			cachedData[dataName] = data
			print(dataName)
			print(data)
			return true
		elseif RS:IsClient() then
			print("Save is running on the client")
			local SE = Events:WaitForChild("Save")
			local s = SE:InvokeServer(key, dataName, data)
			return s
		end
	else
		return false
	end
end
if RS:IsServer() then
	Events:WaitForChild("Save").OnServerInvoke = Datastore.Save()
	Events:WaitForChild("Load").OnServerInvoke = Datastore.Load()
end

For context, ‘RS’ is the RunService, and the module is sitting in ReplicatedStorage.

The Problem:
When I try to run the “Save” function from the client I get this error:
tables cannot be cyclic - Client - Datastore:33

I really hope there is a solution to this so that I can still allow the client to be able to run the ‘Save’ function. If that’s not possible, it’s fine, I can find another way to do it.

1 Like

Can you show us the 33rd line in your module?

It’s present in those code snippets:

local s = SE:InvokeServer(key, dataName, data)
1 Like

Yes, it fires a function from the client which when received on the server performs the action.
It’s all within the same function using “Runservice:IsServer()” and “Runservice:IsClient”

There are certain limits to what can be transfered within bindable/remoteEvents. I think the data parameter has a reference to itself (its metatable’s __index = data). Which is not allowed.

Also, you can only have table arrays in events. It’s just a harsh limitation

Limitations

Subscription

An event can only be subscribed to by one other script at a time. When a second script subscribes to an already subscribed event, the first script will be unsubscribed.

Parameters

If a Table is passed as an argument to a BindableEvent it must be an array without missing entries or have string keys, not a mixture, or else the string keys will be lost.
Source: BindableEvent (roblox.com)

That makes sense. I had forgotten that the RemoteFunction also passed through what fired the function, which is most likely being passed as the module.

Any ideas on how to solve that issue though? that is still FE

Also, bindableFunctions have even more restrictions

Limitations

Invocations will yield until the corresponding callback is found. If the callback was never set, the script that invokes it will not resume execution.

Subscription Only one function can be bound to BindableFunction/Invoke at a time. If you assign multiple functions, only the last one assigned will be used.

Parameter Limitations

Any type of Roblox object such as an Enumeration, Instance , or userdata can be passed as a parameter when a RemoteEvent is fired or a RemoteFunction invoked. Lua types such as numbers, strings, and booleans can also be passed, although there are some limitations on how data can be passed.

Mixed Tables

Avoid passing a mixed table (some values indexed by number and others by key), as only the data indexed by number will be passed . For example, when the server receives the colorData table illustrated below, it will only see indices 1 and 2 containing "Blue" and "Yellow" while the other data will be lost in the transfer. Note, however, that sub-tables do not need to be indexed in the same way as their parent — in other words, as long as each individual sub-table is indexed with the same type, all of the data will be preserved.

Non-String Indices

If any indices of a passed table are non-string type ( Instance , userdata, function, another table, etc.), those indices will be converted to a string.

local colorData = {}
colorData[1] = "Blue"
colorData[2] = "Yellow"
colorData["Color1"] = "Green"
colorData["Color2"] = "Red"
-- Table with two key-indexed sub-tables
local playerData = {}
playerData["CharData"] = {
-- All children indexed by key
CharName = "Diva Dragonslayer",
CharClass = "Knight"
}
playerData["Inventory"] = {
-- All children numerically indexed
"Sword",
"Bow",
"Rope"
}```

#### Functions

Functions passed as parameters will not be replicated, therefore making it impossible to use these objects to pass functions between scripts.

Source: BindableFunction (roblox.com)

Maybe you can try a RemoteEvent instead of a RemoteFunction?

I was thinking about that, I’m going to try it real quick. I will come back here and explain if it worked or not.

1 Like

Okay I tried it and got the same error. I don’t know what the cause could be. Maybe it somehow thinks it could turn into an infinite loop. I don’t really know a lot about this stuff, so thats why I came here.

I mean, it isn’t the best solution, but you could try serializing the table to string via tostring and calling loadstring on it from the server.
Note: You will have to enable LoadStringEnabled in ServerScriptService

That stops the code from erroring, but the system still doesn’t work, so there may be a problem with the way that I am trying to activate the function now. The function is not fired on the server. I’m very new to Module scripts so I may just be doing it wrong:

Events:WaitForChild("Save").OnServerEvent:Connect(function(val)
		Datastore.Save(loadstring(val))
	end)

probably u have a module script A refrencing module script B and module script B is refrencing modules script A creating an infinite loop. U should make one module that references all modules to fix this.

What do you mean by this? I don’t know what you mean by “one module that references all modules”

Your function should be expecting player, val in here, val is replacing player.

I had it like that initially and it still didn’t work.

Nevermind, I was thinking of string.dump loadstring, Roblox removed string.dump for security reasons and tostring does NOT work in its place

I think I could still concatenate all of the values and seperate them with commas. After that I could do string.split(), right? That would have a similar effect?

ModuleTable script:

ModuleTable.ModuleA = Require(ModuleA)
ModuleTable.ModuleB = Require(ModuleB)