Problems Getting SharedTables to be Globally Accessible (As they were built to be)

======================== A quick Introduction… ========================
I’ve really been wanting to ask for help with this for a while, but now, with me being able to post here, I can finally ask for help with this. To preface this, I’m new to Lua scripting, so I apologize if I get some terminology wrong or some small thing unrelated to my issue wrong. If you tell me about it, I’ll be sure to update my topic with that in mind. And another thing: This post is kind of long, so feel free to skip to “What is the Issue?” if you don’t want to read all that. Still, some of your potential questions might be answered in “What do you want to achieve?”, so I still highly encourage you to read through the whole thing if you can.

And with that out stuff of the way, let me describe my issue:


You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
Background Information

I’m trying to make a randomly generated board game, but I keep running into bugs with my generation “algorithm” (if you can even call it that). To help with troubleshooting and whatnot, I’m trying to build a dev tool that lets me see every generation-related error by hovering over the generated part with my mouse. The UI part I was able to square away fine, but storing the information has been a pain in the butt to figure out. After looking around in Roblox’s documentation, I found that ShareTables seemed to be the best way to store data for my particular circumstance. But boy do I regret that now…

How I went about implementing this is kinda convoluted, but I’ll walk you through the best I can:

Step 1: Obtaining the Data
The goals of the dev tool

In order for my dev tool to work properly, I need to 4 types of data for every part:

  1. All the errors the Instance in question tripped
  2. A marker that associates the errors to an Instance
  3. A way to store a marker of the instance itself, so my dev tool can easily look up its information and apply it to my crappy-looking Dev GUI
  4. A list of all pre-existing Instances that had a hand in triggering said errors.
    1. For example, there might be an error that triggers because a generated space is too close to other already-generated board spaces. In a case like this, I would want a list of all board spaces that were deemed to be “too close” to said space.

Keep these 4 things in mind while I go through how my code is run step by step.

Step 1-- Obtaining the data

One of the most important parts of the error storing data is, y’know… getting the data. Everytime the board generation “algorithm” needs to make a new space on the board, it makes an empty table called placeholderSpaceLogs. Anytime a warning or an error is sprung, the error information as well as any other necessary data is logged as an entry in this magical table. This table entry essentially holds all the information needed to complete step 1 and step 4 of our goal (yay-- already halfway done!).

function logError(errorType:string, errorCode:number, failedObject:any, instagators:table, consoleErrorCode:string, isFatal:boolean)

Above is the function that gets called when an error gets called. This is what every parameter does:

  • errorType and errorCode is the Information needed to figure out what specific error has been triggered (I’ll explain their existence later on in the topic)
  • failedObject is the thing (or space) that failed
  • Instigators are the things that caused the failedObject to, well, fail (basically step 4’s goal). Can pass in an empty table if there are no error accomplices
  • consoleErrorCode is for if I ever need to print custom error text to the console rather than a copy/paste generic error
  • isFatal basically desires if Roblox’s warn() or error() calls get ran

Getting back on track, all of this mumbo jumbo doesn’t really explain the weird placeholderSpaceLogs variable name, does it? It’s called "placeholder"SpaceLogs because when the errors originally get tripped, it’s impossible to associate the errors with a specific space! This is because most of the errors the program gets sprung by happen before the board space even gets created. And with nowhere to save or associate the errors with, where else would you put them other than a temporary table? With this in mind, anytime an error gets sprung and the failedObject variable isn’t an instance, we save it to a list. Then, when an instance is passed through the logError function, it takes all the errors saved in the placeholderSpaceLogs and uses a for loop to apply them all to the community ShareTable. But unfortunately, Thanos says it best: Reality is oftentimes Disappointing. And with that, we move on to Step 2 of this whole ordeal.

But first, here’s the complete code to logErrors() now that I’ve explained what it does:

Step 1's Complete Code
function logError(errorType:string, errorCode:number, failedObject:any, instagators:table, consoleErrorCode, isFatal)	
	local errorText = logger.GetErrorFromCode(errorType, errorCode)
	print(typeof(failedObject)) -- Here for debug purposes
	
	-- Record the error details to a placeholder table in case the failedObject in question isn't an instance
	table.insert(placeholderSpaceLogs, {errorType, errorCode, instagators}) 

	if typeof(failedObject) == "Instance" then -- Checks if it's an instance
		-- If failedObject is an instance, it starts the process of porting all the saved placeholder data to said 
		-- object and saves it to spaceLogs, the actual "universal" ShareTable 
		for i, data in pairs(placeholderSpaceLogs) do
			local id, generatedValues = logger.Insert(logger, data[1], data[2], failedObject, data[3]) -- Processes the data to be compatible with the strict sharetables
			print(spaceLogs[id]) -- More debug stuff
			if not spaceLogs[id] then
				-- If the failed object hasn't been added to the sharetable yet, it creates a new sharetable that
				-- stores the newly-processed information

				spaceLogs[id] = SharedTable.new() 
			end
			
			local dataShareTable = SharedTable.new(generatedValues) -- ShareTable.new() is able to convert "compatible" Lua tables into sharetables. A Rare ShareTable W
			print(spaceLogs[id])
			spaceLogs[id][i] = dataShareTable -- Saves the converted data as an entry to a larger spaceLog table
		end
		table.clear(placeholderSpaceLogs) -- Deletes placeholderSpaceLogs for use with another failing space
	else
		-- Debug stuff that prints if the shared file isn't an instance
		print("WARNING: FAILED OBJECT GIVEN IS NOT AN INSTANCE, SO IT'S BEEN ADDED TO A LIST!") 
	end
	
	-- Error handeling code that checks whether to warn the console or error the console templeted or custom 
	-- errors mandated by customErrorText
	if isFatal or errorText[1] == '|' then
		if not consoleErrorCode then
			if errorText[1] == '|' then
				error(string.sub(errorText, 2, string.len(errorText)))
			else
				error(errorText)
			end
		else
			error(consoleErrorCode)
		end
	else
		if not consoleErrorCode then
			warn(errorText)
		else
			warn(consoleErrorCode)
		end
	end
end
Step 2: Storing the Data
Storing the failed instance/Instigators

This part would be simple, if it wasn’t for the restrictions ShareTables have on storing data. Normally, I could just throw all the data gathered from step 1 into a table and be done with it, but you can’t store basically anything in ShareTables that aren’t default Lua constructors, which unfortunately includes instances. So, if you can’t directly store instances, then what kind of data could you store that can directly lead to the instance?

After doing a quick glance through the property window of an instance, I thought that Roblox had basically solved this problem for me through their implementation of something called “UniqueId”. But it quickly became apparent that Roblox just wanted to do a little trolling because IT IS MOST LITERALLY THE ONLY DATA TYPE IN THE PROPERTIES MENU THAT IS NOT ACCESSIBLE THROUGH SCRIPTS. Thanks, Roblox.

So after that setback, I decided just to make my own version of UniqueIds and apply it as an attribute (because that’s more convenient for my circumstances than tags). As such, I had to make a new Sharetable dedicated just to storing the UniqueIds because I needed multiple scripts to access this information. The entire logic path looks like this:

Visual Representation (Sorry, Canva Free sucks)

Instance-removebg-preview

The code for this looks like so:

Data Storing Code
function insertWithAffected(errorType:string, errorCode:number, spaceInstance:Instance, instagators:any):boolean
	local convertedInstagators = SharedTable.new() 
	-- A Sharetable that stores a Share-Tab-Compatible version of the instagators variable
	local spaceLogRow = {}
	-- This is the final thing that gets added to the sharetable
	local convertedInstance
	-- Stores a ShareTable-Compatible version of the failed Object (AKA the spaceInstance Variable)
	local instanceId
	-- Stores a ShareTable-Compatible version of 1 variable inside of instagators. Use
	
	-- CONVERTING THE INSTAGATORS TO BE SHARE-TABLE COMPATIBLE
	for i, object in pairs(instagators) do
		if typeof(object) == "Instance" then 
			-- Checks if it's an instance, since Sharetables can't store them directly :p
			warn("IT IS TOTALLY AN OBJECT") -- Some debug stuff
			local instigatorId = object:GetAttribute("UniqueId")
			print("Saved Attribute ID:", instanceId)
			if not instigatorId then 
				-- If this is triggered, then that means that this isn't a registered ID so
				-- we have to create a new one for it
				print("This object hasn't been seen before! Generating a new ID...")
				instigatorId = uniqueId:GerateUniqueId(object) 
				-- ^^Generates a Unique ID and adds it to a special Sharetable
			end
			object = instigatorId
		end
		convertedInstagators[i] = object
	end
	print(convertedInstagators) -- Some more debug stuff
	
	-- Converts Generic Lua DataTypes (bool, string, number, etc.) and some Roblox DataTypes
	-- to Instances so they can be stored on the spaceLogs ShareTable. Thanks to how my code 
	-- works now, none of this should ever be called and this is outdated code
	if typeof(spaceInstance) ~= "Instance" then
		if type(spaceInstance) == "table" then
			-- Makes sure that there aren't multiple failed Objects
			error("Only 1 thing can fail at once!")  
		elseif type(spaceInstance) ~= "userdata" then
			-- Converts Generic Lua Datatypes to an Instance by captializing the first letter 
			-- of its type() and adding "Value" to the end of it
			convertedInstance = Instance.new(string.upper(string.sub(type(spaceInstance), 1, 1)) .. string.sub(type(spaceInstance), 2, string.len(type(spaceInstance))) .. "Value")
		else
			-- Tries to convert it to a datatype that is an existing roblox datatype. If it's 
			-- not, it defaults to making it a StringValue
			local success, returnedInstance = pcall(function(spaceInstance)
				return Instance.new(typeof(spaceInstance))
			end)
			if not success then
				convertedInstance = Instance.new("StringValue")
				convertedInstance.Value = tostring(spaceInstance)
			else
				convertedInstance = returnedInstance
			end
		end
		-- After the Instance has been created, it fills it with its corresponding data
		convertedInstance.Value = spaceInstance
		convertedInstance.Name = convertedInstance.ClassName
		convertedInstance.Parent = script.Parent
	else
		convertedInstance = spaceInstance
	end
	
	-- Creates a UniqueId for the failedObject if there isn't one already
	if not spaceInstance:GetAttribute("UniqueId") then
		instanceId = spaceInstance:GetAttribute("UniqueId")
	else
		instanceId = uniqueId:GerateUniqueId(convertedInstance)
	end
	-- Preps the row entry for this specific failed object
	spaceLogRow["Error Type"] = errorType
	spaceLogRow["Error Code"] = errorCode
	spaceLogRow["Instagators"] = convertedInstagators
	
	print(spaceLogRow) -- More Debug stuff
	return instanceId, spaceLogRow 
	--^^ Returns the information needed for logError() to add the information to SpaceLogs
end

There’s a insertWithoutAffected version of this, which is basically just this without the instagator-conversion code. It decides whether to use either one with some logic put into the Insert() function that actually gets called in the logError() code. It looks like this:

function module:Insert(errorType:string, errorCode:number, spaceInstance:any, instagators:any):boolean
	if not instagators then
		return insertWithoutAffected(errorType, errorCode, instagators)
	else
		return insertWithAffected(errorType, errorCode, spaceInstance, instagators)
	end
end

It’s so simple that I don’t think I need to explain it much lol

Storing the Error Messages

Finally, I think it’s high time I explain what the errorType and errorCode variables are for. Sometimes, the errors flagged in my program are quite long, and when I return the original flagged error, it appears too long in my dev UI (which this is being made for) and it messes up the formatting I made specifically for the UI. So, I decided to make a third share table that solely stores pre-written, generic versions of each error that gets called by my program. They are organized by what type of error gets called (Failing Space verification, failing Event-related errors, etc.) and the index of where the error is stored in that error type. A snippet of this error code table is shown below:

Error Code Table Example Snippet
local errorTable = {
    ["Generation"] = {
    	[1] = "Success! Space is good to generate",
    	[2] = "Unknown Placement Type While Verifying!",
    	[3] = "Unknown Placement Type During Placement! Defaulted to Forward",
    	[4] = "Attempted Placement While Cooldown Was Active!",
    	[5] = "Attempted Placement While At Consecutive Limit!",
    	[6] = "Attempted Placement While At Max Board Capacity!",
    	[7] = "2D Space Detection is Currently Not Supported",
    	[8] = "Attempted Placement That Violates Max Padding!",
    	[9] = "|Max Attempts to Verify Placement Reached!"
    }
...
}

return SharedTable.new(errorTable)
  1. What is the issue? Include screenshots / videos if possible!
Last thing of context before the issue, I swear

For all of the stuff talked about in “What do you want to achieve?”, I have each part of the process stored in different scripts for organizational reasons. The layout is like so:

Game Storage Layout
🎮 game
┣ 💾 ReplicatedStorage
| ┗ 📂 Values
| | ┗ 📂 SystemValues
| | | ┗ 📂 ErrorCollection
| | | | ┣ 📄 (ModuleScript) ErrorCodeTable
| | | | ┣ 📄 (ModuleScript) GenerateUniqueIds (houses Instance to UniqueID conversion code and adds the generated UniquId to the UniqueId SharedTable)
| | | | ┗ 📄 (ModuleScript) SpaceErrorLogs (houses the logic used to prep errors to be logged to the main SpaceErrorLogs SharedTable)
┗ ☁️ ServerScriptService
  ┗ 📄 (Script) Main (Here is where the SpaceLog and UniqueId SharedTables are created)
    ┗ 📄 (ModuleScript) BoardGenerator (Here is where logError() resides and the code that has the potential to call it resides)

If you want to know what each of these scripts do, see “What do you want to achieve?”.

The Issue

Essentially, when I save things to the SharedTables, nothing ever gets saved to them. When I run the code and try to get the ShareTable content of the 3 Sharetables I made, they always come up as empty brackets, even though I seemingly do everything correctly (at least going by Roblox’s Documentation for SharedTables). No idea why this is.

  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    Aside from making sure all the tables are created and stored in the SharedTableRegistry Service, I have no idea what else to try-- I’m not an experienced enough Lua scripter to know where to go from here. I’ll be sure to update this section as I try solutions suggested by the gracious people helping me out with this issue, and I’ll leave the running list of them below:
Things I've Tried

None yet, as this post has just been created. Check back here later when more replies are made :slight_smile:

Besides that, thank you so much for reading this entire thing! I was scared that the length of this topic would scare people away, so I’m glad that’s not the case. Tell if you guys need anymore information from me, and I’ll update the space below with it! Good luck :slight_smile:

Information Requested by the thread

Nothing yet-- I just made this topic!


Edit log:

7/8/24, 11:58PM (MST)

Topic Created

The solution was pretty simple, and I never realized it until now: The scripts for dealing with the ShareTables in ServerScriptService have to be on the server, not ReplicatedStorage (See the main post’s “Last thing of context before the issue, I swear” under “What is the Issue?” if you’re confused). Mind you, this was not obvious at all-- the scripts in ReplicatedStorage not only ran fine and were able to take parameters from the script in ServerScriptService, but the debug print() statements I had in those ReplicatedStorage scripts had the green tag thing in the terminal, which meant that they were still running on the server. If anyone could explain why that is, I would really appreciate that lol. Or, everyone could just keep ignoring this post like they did before lol. Hope this helps some poor soul out there other than me