Global Framework

This happens because ModuleScript threads never die they stay suspended forever
this causes the Global module to wait forever there are a few ways we can solve this problem

Option 1

When calling Global in a ModuleScript place it in a new thread that will die


Option 2

Call the Global.Start() function at the bottom of your ModuleScript this will unregister the thread and allow the Global module to stop waiting

but if you add Global.Start() at the bottom of your ModuleScript this will cause the script that required the module to also wait until the ModuleScript returns if you don’t want the script to wait you can do this

local Module = task.spawn(require, ServerStorage.ModuleScript)

Option 3

Don’t use ModuleScripts like this

You could just change your ModuleScript into a script and it should all work fine


Option 4

Make a new function that unregisters the thread without yielding

1 Like

Global Library Updated 1.3

image

Added SignalManager & Table
bug fix Thread


Fixed typo in Thread and functions now return the correct type

Table provides two commonly used functions Clone & Reconcile

SignalManager is used to help manage signal connections

function Character.new(instance)
	local self = Global.Metatable(Character) :: Global.Character
	-- Create a SignalManager and connect two signals
	self.Signals = Global.SignalManager()
	self.Signals:Connect(instance.AttributeChanged, Character.AttributeChanged, self)
	self.Signals:Connect(instance.AnimationController.Animator.AnimationPlayed, Character.AnimationPlayed, self)
	return self
end

function Character:AttributeChanged(attribute)
	print(attribute)
end

function Character:AnimationPlayed(animationTrack)
	print(animationTrack)
end

function Character:Destroy()
	-- Disconnect all signals from the SignalManager
	self.Signals:DisconnectAll()
end
2 Likes

Global Library Updated 1.4

image

Removed <P> generic from ParameterEvent, ParameterTimer and SignalManager because there where some small edge cases where the type checking engine would fail

Added Table2D and Table3D

-- Make a 2d table that has string values and number keys
local table2D = Global.Table2D()-- :: Global.Table2D<string, number>

-- Add ABC to index 5x2
table2D:Set(5, 2, "ABC")

print(table2D:Get(5, 2))

-- Remove ABC from index 5x2
table2D:Set(5, 2, nil)
2 Likes


typeError signalManager needs capital M

Thank you for reporting this I have fixed it :slight_smile:

1 Like

I noticed that this also happens even with a regular script, when you put a return at it’s last line. Not something that I would do normally, Just something that I found randomly, wanted to let you know :slight_smile:

I just tested this for myself and everything worked perfectly fine with a return at the bottom of a script so I’m guessing the problem you experienced was with something else

1 Like

Oh strange, I tried reproducing it as well again and it seems to be working fine. Must have been something else like you said. Unfortunately I no longer have the project file with the bug I mentioned earlier. I’ll let you know if I find it again :sweat_smile:

Hi! In your next update can you add support for writing custom code before Global framework adds its own types?

^
I want to use FastSignal module with this framework. However, to use this module’s types properly, I must require it before the global’s types are set.

The reason I’m requiring modules and setting it’s types manually, as shown above, is because it is currently difficult to add types using the bracket comments method with modules that don’t have the global framework in mind. For example, I’m using Trove which is a cleanup module by Sleitnick. In order to add the Trove type through the bracket comments method provided by the plugin, I would need to heavily modify the original code, as Trove has a multitude of types it uses within it.

To get around this and simplify the process, inside the global framework module under the ‘local globals = {}’ line, I require the trove module and simply get its type though there. And then afterwards I can use the Trove type though the Global framework normally. Currently this is the only place where edits to the module are not overwritten by the plugin.

image

This works fine with trove, However with FastSignal its a different story since it uses generics.

image

^ If I attempt to do this with the method I talked about, then every time I try to use the FastSignal type through Global, I can’t input my own arguments inside the <> brackets.

To be able to use the generics and global framework properly, I need to require FastSignal before the Global’s types are set. That way in my other scripts I can do the following:

image

here is one option of how you can bring a external module into global

--[[type TroveConstructor = typeof(require(game.ServerStorage.Trove))]]
--[[type Trove = typeof(require(game.ServerStorage.Trove).new())]]

local Global = require(script.Parent)

Global("Trove", require(game.ServerStorage.Trove) :: Global.TroveConstructor)

and here is a example of a class using the trove type

in other modules you might not even need the TroveConstructor type but because trove is setup a bit differently where it returns a separate table

image

this is why we had to make the separate TroveConstructor

1 Like

That’s intresting :thinking:
How would I do this with FastSignal that needs generics arguments?

I tried doing this but it isn’t working:

But I really think that allowing to let people write code above the global types is the best way to do this.

Global Library Updated 2.0

image


all objects/classes now have a Type property

local collection = Global.Collection()
print(collection.Type) -- Collection


Fixed Wait method in the Timer class


Event and ParameterEvent unified into a single class

local event = Global.Event()

-- connect to Event
event:Connect(function()

end)

-- connect to Event with a parameter
event:ParameterConnect("Parameter", function(par)
    print(par) -- Parameter
end)

SignalManager now accepts RBXScriptSignals and Events

local signals = Global.SignalManager()

-- connect a RBXScriptSignal
signals:Connect(instance.AttributeChanged, AttributeChangedFunction)

-- connect a RBXScriptSignal with a parameter
signals:Connect(instance.AttributeChanged, AttributeChangedFunction, "Parameter")

local event = Global.Event()

-- connect a Event
signals:Connect(event, EventFunction)

-- connect a Event with a parameter
signals:Connect(event, EventFunction, "Parameter")
1 Like

Global Library Updated 2.1

Added BatchFire for events

local event = Global.Event()

event:Connect(function(value)
    print(value) -- this will print 4 and 8 only
end)

event:BatchFire(1)
event:BatchFire(2)
event:BatchFire(3)
event:BatchFire(4)
task.defer(function()
    event:BatchFire(5)
    event:BatchFire(6)
    event:BatchFire(7)
    event:BatchFire(8)
end)

event:Fire() works the same as before

3 Likes

Module + Plugin Update

added

for index, module in game:GetService("CollectionService"):GetTagged("GlobalModuleScript") do module = require(module) :: any end

to the global module


and for the plugin a variable can now point to multiple global modules

local MyVariable
MyVariable = require(game.pathto.GlobalModule1)
MyVariable = require(game.pathto.GlobalModule2)
MyVariable = if true then require(game.pathto.GlobalModule3) else require(game.pathto.GlobalModule4)

in the example above MyVariable will point to GlobalModule1, GlobalModule2, GlobalModule3 and GlobalModule4


this is useful as its now allows us to make ModuleScripts that can run on both the server side and client side

:warning: its not safe to use Global.Initialize() or Global.Start() inside modules but it is safe to use Global.Initialize(function() end) or Global.Start(function() end)

2 Likes

Hey 5uphi, I’ve been trying to use the module shared between the server and client, but it’s not working. So, I made this test, but I’m still having issues.

Module:

--[[type MyClass = {
    Value: number,
    new: () -> MyClass,
    Print: (self: MyClass) -> (),
}]] 

local Global = require(game.ReplicatedStorage.Global)
if game:GetService("RunService"):IsServer() then 
    Global = require(game.ServerScriptService.Global) :: any 
end

local class = Global("MyClass", Global.Metatable()) :: Global.MyClass

function class.new()
    local self = Global.Metatable(class) :: Global.MyClass
    self.Value = math.random(1, 99)
    return self
end

function class:Print()
    print(self.Value)
end

local text = if game:GetService("RunService"):IsServer() then "server" else "client"
warn("Of module to " .. text, Global.MyClass)

return nil

Client Script:

local Global = require(game.ReplicatedStorage.Global)
Global.Start()

warn("Client", Global.MyClass)

task.wait(3)

warn("Client", Global.MyClass)

Server Script:

local Global = require(game.ServerScriptService.Global)
Global.Start()

warn("Server", Global.MyClass)

task.wait(3)

warn("Server", Global.MyClass)

The output I’m getting:

19:29:20.401  Server nil  -  Server - Server:4
19:29:20.609  Client nil  -  Client - Client:4
19:29:22.141  Of module to client  ▶ {...}  -  Client - Module:24
19:29:22.090  Of module to server  ▶ {...}  -  Server - Module:24
19:29:23.410  Server nil  -  Server - Server:8
19:29:23.611  Client nil  -  Client - Client:8

Issue:

  • As you can see, Global.MyClass returns nil on both the client and the server, even after running Global.Start().
  • However, the warning inside the module ("Of module to ...") seems to return the correct table.

Is there something wrong with how I’m requiring or initializing the module?

Thanks in advance!

Your code looks fine

when I tested your code everything works fine

here is the project with the same code you sent
Test.rbxl (57.8 KB)

Module Update

removed

for index, module in game:GetService("CollectionService"):GetTagged("GlobalModuleScript") do module = require(module) :: any end

currently doing

local Global = require(game.PathTo.ClientModule)
if game:GetService("RunService"):IsServer() then 
    Global = require(game.PathTo.ServerModulel) :: any 
end

works fine with the current type checking engine but I believe this hack will stop working in the next type checking engine so I do not recommend using it

for this reason I have removed the module loading part from the global module

:sob:

once the new type engine comes out of beta ill take a second look at this and see if there are any good options

3 Likes

Global Library Updated 2.2

Added Benchmark

RobloxStudioBeta_jS8Ix4WR2x

local Global = require(script.Parent)

Global.Start()

game:GetService("RunService").Heartbeat:Connect(function()
	local benchmark = Global.Benchmark("Clock")
	os.clock()
	benchmark:End()

	local benchmark = Global.Benchmark("Time")
	time()
	benchmark:End()

	local benchmark = Global.Benchmark("for")
	for index = 1, 100 do end
	benchmark:End()
end)

:warning: uses UIDragDetector this is currently a beta feature make sure to enable it

Hello @5uphi,
First of all thank you for your contribution to the community.

I have been playing around with your framework and I have some questions:

  1. Global.ServerEvent

I have some local scripts on StarterCharacterScripts connected to RemoteEvents, but due to streaming those objects to whom the server scripts are attached are not existing yet.
This throws error

ReplicatedStorage.Global.Library.ServerEvent:24: attempt to index nil with ‘OnClientEvent’

Due to the fact that the constructor is waiting for the server to create the RemoteEvent

Global("ServerEvent", function<A...>(name, func)
	local self = serverEvents[name]
	if self then return self end
	local remoteEvent = folder:WaitForChild(name) -- <-- HERE
	if func then remoteEvent.OnClientEvent:Connect(func) end
	local self = Global.Metatable({RemoteEvent = remoteEvent}, ServerEvent) :: Global.serverevent<A...>
	serverEvents[name] = self
	return self
end :: <A...>(name: string, func: (A...) -> ()?) -> Global.serverevent<A...>)

This is my code

Global.ServerEvent("Trigger", function(check)
	if check == true then
		-- do something
	end
end)

How would you suggest to work around this? I’ve tried with the following but I’ve noticed it is only causing replication of the RemoteEvents, thus not solving the issue

local remoteEvent
if folder:FindFirstChild(name) then
	remoteEvent = folder:WaitForChild(name,)
else
	remoteEvent = Instance.new("RemoteEvent")
	remoteEvent.Name = name
	remoteEvent.Parent = folder
end
  1. PlayerData

I have been following your video with the example of creating global PlayerData, which I find really clever. I have connected that to the dataStore using your module, yet I am missing the saving events. I wanted to create an event to trigger every changes in PlayerData in order to update some custom UIs, so I’ve set up a module to create a proxy table that recursively gets updated in order to match the PlayerData structure (no matther the depth) and thanks to __newindex I’m capable to trigger the event. I’m using parentPath in order to get the path for the data structure.

This is the fuction in my custom module

local function wrap(value: any, dataChangedEvent, parentPath: string?)
	if typeof(value) ~= "table" then
		return value
	end
	
	local real = {}

	for k, v in pairs(value) do
		local childPath = parentPath and (parentPath .. "." .. k) or k
		real[k] = wrap(v, dataChangedEvent, childPath)
	end

	local proxy = {
		_path = parentPath,
	}

	setmetatable(proxy, {
		__index = function(_, key)
			return real[key]
		end,

		__newindex = function(_, key, newValue)
			local oldValue = real[key]
			local fullPath = parentPath and (parentPath .. "." .. key) or key
			newValue = wrap(newValue, dataChangedEvent, fullPath)

			if oldValue ~= newValue then
				real[key] = newValue
				dataChangedEvent:Fire(fullPath, oldValue, newValue)
			else
				real[key] = newValue
			end
		end,

		__iter = function(_)
			return next, real
		end,
	})

	return proxy
end

function PlayerData.Create()
	local dataChangedEvent = Global.Event()
	local dataProxy = wrap({}, dataChangedEvent, nil)
	return dataProxy, dataChangedEvent
end

In my PlayerObject script I’m setting

local proxy, dataChangedEvent = playerDataModule.Create()
self.Data = proxy
self.DataChanged = dataChangedEvent

Then, when successfully opening the dataStore I do the following

local function deepMerge(proxyTarget, source)
	for key, newValue in pairs(source) do
		local oldValue = proxyTarget[key]

		if typeof(newValue) == "table" then
			if typeof(oldValue) ~= "table" then
				proxyTarget[key] = {}
				oldValue = proxyTarget[key]
			end
			deepMerge(oldValue, newValue)
		else
			proxyTarget[key] = newValue
		end
	end
end

deepMerge(self.Data, dataStore.Value)

This way I can connect to the event when data are updated and send a Remote event to the client

local function toPlainTable(proxyOrValue)
	if typeof(proxyOrValue) ~= "table" then
		return proxyOrValue
	end
	local result = {}
	for k, v in proxyOrValue do
		result[k] = toPlainTable(v)
	end
	return result
end

self.DataChanged:Connect(function(key, oldValue, newValue)
		local printableNew = typeof(newValue) == "table" and toPlainTable(newValue) or newValue
		local printableOld = typeof(oldValue) == "table" and toPlainTable(oldValue) or oldValue

		print((">> Data changed for %s | key=%s : %s => %s"):format(
			self.Instance.Name,
			tostring(key).split(key,".")[1],
			tostring(printableOld),
			tostring(printableNew)
			))
		
		Global.ClientEvent(tostring(key).split(key,".")[1]):Fire(self.Instance, key, newValue)
	end)

Is there a way to integrate the wrap function directly in your Global framework instead of importing an external module?

Thanks a lot

I don’t really understand why remoteEvent is nil when you use WaitForChild it should wait for the remoteEvent to replicate


In the discord server I have a post in resources called Keep Track of table changes that has example code on how to keep track of table changes