Issue with table in module?

Hello!

I feel stupid asking this because I’ve used modules so many times but I can’t seem to get the table to return the proper result for some reason?

I have a module called UIUtil which is a UI utility module, it handles opening and closing UIs.

I have a function called “RegisterUI” which recognizes the UI, creates necessary connections etc, and most importantly adds it to a table to save the state.

However, when I require it from the command bar, the OpenUIs table just clears.
It should be a dictionary where the frame is the index and the value is a bool dictating whether it’s open or not.

print(require(game.ReplicatedStorage.Modules.UIUtil)) -- OpenUIs table is {}

Module:

local tweenService = game:GetService('TweenService')
local userInputService = game:GetService('UserInputService')

local module = {
	['CreateNotice'] = function(self, successful: boolean, returnTable: { -- not important
		promptStatus: boolean;
		notice: string;
		})
		script.Parent.Enabled = returnTable.promptStatus
		if returnTable.notice then

		end
	end;
	['OpenUIs'] = {}; -- table where they should be added
	['RegisterUI'] = function(self, uiObject: Frame, initialState: boolean?) -- function that adds to the table
		local alreadyRegistered = self.OpenUIs[uiObject] ~= nil
		if alreadyRegistered then
			return warn(uiObject, 'is already registered!')
		end
		self.OpenUIs[uiObject] = initialState == true
		print(self.OpenUIs)
		for i,v in ipairs(uiObject:GetDescendants()) do
			if v:IsA('GuiButton') and v.Name == 'CloseButton' then
				v.MouseButton1Click:Connect(function()
					self:Close()
				end)
			elseif v:IsA('ObjectValue') and v.Name == 'OpenFrame' then
				v.Parent.MouseButton1Click:Connect(function()
					self:Open(v.Value)
				end)
			end
		end
		if initialState then
			self:Open(uiObject)
		end
	end;
	['OpenByName'] = function(self, name)
		print(self.OpenUIs)
		for i,v in pairs(self.OpenUIs) do
			if i.Name == name then
				self:Open(i)
			end
		end
	end,
	['Open'] = function(self, uiObject)
		if self.OpenUIs[uiObject] == nil then
			return warn(uiObject, 'isn\'t registered!')
		end
		for i: Frame,v: boolean in pairs(self.OpenUIs) do
			if v and i ~= uiObject then
				self.OpenUIs[i] = false
				tweenService:Create(i, TweenInfo.new(0.5), {Position = UDim2.new(0.5,0,-0.5,0)}):Play()
			end
		end
		self.OpenUIs[uiObject] = true
		print('a')
		tweenService:Create(uiObject, TweenInfo.new(0.5), {Position = UDim2.new(0.5,0,0.5,0)}):Play()
	end;
	['Close'] = function(self)
		for i,v in pairs(self.OpenUIs) do
			if v then
				tweenService:Create(i, TweenInfo.new(0.5), {Position = UDim2.new(0.5,0,-0.5,0)}):Play()
				self.OpenUIs[i] = false
				if i:FindFirstChild('OpenOnThisFrameClosed') then
					self:Open(i)
				end
			end
		end
	end;
}

module.UserInputServiceInputBeganConnection = module.UserInputServiceInputBeganConnection or userInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent then
		return
	end
	if input.KeyCode == Enum.KeyCode.Backspace or input.KeyCode == Enum.KeyCode.Escape then
		module:Close()
	end
end)

return module

Any ideas?

print(require(game.ReplicatedStorage.Modules.UIUtil))

You haven’t indexed the OpenUIs key.

1 Like

That prints the table which contains the table with the key OpenUIs anyway.

Module Scripts are only run once in every environment. The Command Bar has its own environment, hence the reason why it doesn’t sync with the game server/client environment. All you can really do is insert a localscript/script into PlayerStarterScripts/ServerScriptService and solo play test the game

So you’re saying running a command in the command bar on the client and running a command in a LocalScript aren’t the same? Wouldn’t that mean bindables would break too? I can fire a bindable event fine.

Wouldn’t that mean I also couldn’t access players.LocalPlayer? I can do that fine as well.

The Command Bar is not exactly affected by replication, only its environment is affected (which is what the ModuleScript runs in). From what I’ve tested, it seems the environments are Client, Server, Studio Client, and Studio Server. Studio Client and Client replicate with each other (which is why calling :Fire on your bindables still works). If you’re still confused, this is how it works.

Environment calls require for the first time > ModuleScript runs in that environment > Any other calls of require in that environment return the same value that was returned the first time

That’s why tables returned from ModuleScripts are not replicated from the Server to the Client (and vice versa but the Client never replicates to the Server anyways)

1 Like

Right, but I’m not sending anything across the client-server boundary, everything’s staying on the client, however you say there’s 4 sort of environments, so Studio Client and Client aren’t the same? I guess that makes sense but it seems weird that it wouldn’t replicate, even _G replicates. :man_shrugging: Oh well. Thanks!

If you’re calling require on two different environments on one module that returns some sort of object value (most commonly a table), you’re constructing an object two different times. Objects can never equal eachother. These objects will never be the same. Changing a value on that object on the server? Won’t replicate to the client (with Roblox’s replication system at least). With the _G thing, I haven’t done testing on it, but based on your description, the _G is actually not a table but a wrapper of a table (like an instance) that is also part of the replication process. Hope this helps

Edit: _G is actually a table and similarly, it does not replicate. Also, similarly to ModuleScripts, _G looks to be constructed independent to the environment. The following screenshot is just my experimentation and further proof of the 4 environments
image