Global Framework

Global Framework

Download Plugin | Download Library | Video Tutorial | Discord Server

Features

Global Types                         "Access types from all other scripts"
Global Variables                     "Access variables from all other scripts"
Strict Mode                          "--!strict"
Circler Dependencies                 "Circler types + circler variables"
Inheritance                          "Inherit via 2 modes [Clone, Metatable]"
Automatic inherit dependency sorting "No mater what order your scripts run in"
Automatic generic dependency sorting "Global generic types will be listed in the correct order"
Diagnostics                          "Warnings if you define the same type or variable"
Comments Within Comments             "O_O"
Metatable Shortcut                   "Metatable types are very easy to type"
Style Flexibility                    "Does not enforce a specific style of writing code"
Modular                              "Only use parts of the global library you want"
Customizable                         "Very easy to add/modify/remove framework functions"

Support My Work

If you liked my work and want to donate to me you can do so here


SourceCode

You can get the sourcecode to this plugin/module here

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.


Quick Start

Step 1

"Make a ModuleScript and give it a attribute called GlobalTypes and set it to true"

690x458


Step 2

"Make a Script and require the ModuleScript we just made"

690x458


Step 3

"Make a type and block comment it out using --[[type]]"

690x458


Step 4

"Add a value into Global and set its type"

690x458


Done

"Congratulations you now have a global value that you can access in other scripts"

690x458


CLASS WITH METATABLE EXAMPLE

--!strict

--[[type MyGlobalClassType = {
    __index:                 MyGlobalClassType,
    new:                     (name: string, age: number) -> MyGlobalObjectType,
    SetName:                 (self: MyGlobalObjectType, name: string) -> (),
    SetAge:                  (self: MyGlobalObjectType, age: number) -> (),
}]]

--[[type MyGlobalObjectType  = {
    Name:                    string,
    Age:                     number,
} MyGlobalClassType]]

local Global = require(game.PathTo.ModuleScript)

local class = Global("MyGlobalIndex") :: Global.MyGlobalClassType
class.__index = class

function class.new(name, age)
    local self = setmetatable({}, class)
    self.Name = name
    self.Age = age
    return self
end

function class:SetName(name)
    self.Name = name
end

function class:SetAge(age)
    self.Age = age
end

CLASS WITHOUT METATABLE EXAMPLE

--!strict

--[[type MyGlobalObjectType  = {
    Name:                    string,
    Age:                     number,
    new:                     (name: string, age: number) -> MyGlobalObjectType ,
    SetName:                 (self: MyGlobalObjectType, name: string) -> (),
    SetAge:                  (self: MyGlobalObjectType, age: number) -> (),
}]]

local Global = require(game.PathTo.ModuleScript)

local class = Global("MyGlobalIndex", Global.Metatable()) :: Global.MyGlobalObjectType 

function class.new(name, age)
    local self = Global.Metatable(class) :: Global.MyGlobalObjectType
    self.Name = name
    self.Age = age
    return self
end

function class:SetName(name)
    self.Name = name
end

function class:SetAge(age)
    self.Age = age
end

CLASS WITHOUT NEW EXAMPLE

--!strict

--[[type MyGlobalObjectType  = {
    Name:                    string,
    Age:                     number,
    SetName:                 (self: MyGlobalObjectType, name: string) -> (),
    SetAge:                  (self: MyGlobalObjectType, age: number) -> (),
}]]

local Global = require(game.PathTo.ModuleScript)

local class = Global.Metatable() :: Global.MyGlobalObjectType 

Global("MyGlobalIndex", function(name, age)
    local self = Global.Metatable(class) :: Global.MyGlobalObjectType
    self.Name = name
    self.Age = age
    return self
end :: (name: string, age: number) -> Global.MyGlobalObjectType)

function class:SetName(name)
    self.Name = name
end

function class:SetAge(age)
    self.Age = age
end

Alternative Modules

Wait replaces Initialize with Wait

local exports:any, link:any, threads:any, completed:any, waitingIndex:any, waitingStart:any = {}, {}, {}, {}, {}, {}
link.Previous, link.Next = link, link

local function Register(thread: thread)
	local threadData = threads[thread]
	if threadData then return threadData end
	local threadData = {Thread = thread, Indices = {}, Previous = link.Previous, Next = link}
	link.Previous.Next, link.Previous, threads[thread] = threadData, threadData, threadData
	return threadData
end

local function End(thread: thread)
	local threadData = threads[thread]
	if threadData == nil then return end
	for index in threadData.Indices do
		completed[index] = true
		if waitingIndex[index] then
			for thread in waitingIndex[index] do task.spawn(thread) end
			waitingIndex[index] = nil
		end
	end
	threadData.Previous.Next, threadData.Next.Previous, threads[thread] = threadData.Next, threadData.Previous, nil
end

local function WaitIndex(index: any)
	if waitingIndex == nil then return end
	if completed[index] then return end
	local thread = coroutine.running()
	if waitingIndex[index] then waitingIndex[index][thread] = true else waitingIndex[index] = {[thread] = true} end
	local threadData = threads[thread]
	if threadData then threadData.Previous.Next, threadData.Next.Previous = threadData.Next, threadData.Previous end
	coroutine.yield()
	if threadData then threadData.Previous, threadData.Next, link.Previous.Next, link.Previous = link.Previous, link, threadData, threadData end
	waitingIndex[index][thread] = nil
end

local metatable = {
	__call = function(globals: any, index: any, value: any)
		if threads then Register(coroutine.running()).Indices[index] = true end
		if value == nil then value = {} end
		globals[index] = value
		return value
	end,
	__index = {
		Wait = function(...: global_indices)
			for index, waitingIndex in {...} do WaitIndex(waitingIndex) end
		end,
		Start = function(func: () -> ()?)
			if func then
				if waitingStart then table.insert(waitingStart, func) else task.defer(func) end
			elseif waitingStart then
				local thread = coroutine.running()
				End(thread)
				table.insert(waitingStart, thread)
				coroutine.yield()
			end
		end,
		Export = function(index: any, value: any)
			if threads then Register(coroutine.running()).Indices[index] = true end
			exports[index] = value
		end,
		Inherit = function(index: global_indices, destination: any, mode: ("Metatable" | "Clone")?, ...)
			WaitIndex(index)
			local source = exports[index] or globals[index]
			if source then
				if mode == "Clone" then
					local indices = {...}
					if #indices == 0 then
						for index, value in source do if destination[index] == nil then destination[index] = value end end
					else
						for index, value in indices do if destination[value] == nil then destination[value] = source[value] end end
					end
				else
					setmetatable(destination, source)
				end
			else
				local path, line = debug.info(coroutine.running(), 2, "sl")
				warn(string.format("%s:%d: Failed to inherit: %s, index not found", path, line, index))
			end
		end,
		Metatable = function(table1: any, table2: any)
			if table1 then
				return if table2 then setmetatable(table1, table2) else setmetatable({}, table1)
			else
				table1 = {} table1.__index = table1
				return table1
			end
		end,
	},
}

task.defer(function()
	local threadData = link.Next
	while true do
		if link == threadData then
			if link.Next == link then break else task.wait() end
		elseif coroutine.status(threadData.Thread) == "dead" then
			End(threadData.Thread)
		end
		threadData = threadData.Next
	end
	for index, threads in waitingIndex do
		for thread in threads do
			local path, line = debug.info(thread, 3, "sl")
			warn(string.format("%s:%d: Infinite yield for index: %s", path, line, index))
		end
	end
	for index, functionOrThread in waitingStart do task.defer(functionOrThread) end
	link, threads, completed, waitingIndex, waitingStart = nil, nil, nil, nil, nil
end)

return setmetatable(globals, metatable)

Initialize default

local threads, initializeThreads, initializes, starts, exports, inheritances = {} :: any, {} :: any, {} :: any, {} :: any, {} :: any, {} :: any

local metatable = {
	__call = function(globals: any, index: any, value: any)
		if threads then threads[coroutine.running()] = true end
		if value == nil then value = {} end
		globals[index] = value
		return value
	end,
	__index = {
		Initialize = function(func: () -> ()?)
			if func then
				if initializes then table.insert(initializes, func) else task.defer(func) end
			elseif initializes then
				local thread = coroutine.running()
				if threads then threads[thread] = nil end
				table.insert(initializes, thread)
				coroutine.yield()
			end
		end,
		Start = function(func: () -> ()?)
			if func then
				if starts then table.insert(starts, func) else task.defer(func) end
			elseif starts then
				local thread = coroutine.running()
				if threads then threads[thread] = nil end
				if initializeThreads then initializeThreads[thread] = nil end
				table.insert(starts, thread)
				coroutine.yield()
			end
		end,
		Export = function(index: any, value: any)
			exports[index] = value
		end,
		Inherit = function(index: global_indices, destination: any, mode: ("Metatable" | "Clone")?, ...)
			if inheritances then
				local inheritance = inheritances[destination]
				if inheritance == nil then inheritance = {} inheritances[destination] = inheritance end
				table.insert(inheritance, {index, mode, {...}} :: {any})
			else
				local source = exports[index] or globals[index]
				if source then
					if mode == "Clone" then
						local indices = {...}
						if #indices == 0 then
							for index, value in source do if destination[index] == nil then destination[index] = value end end
						else
							for index, value in indices do if destination[value] == nil then destination[value] = source[value] end end
						end
					else
						setmetatable(destination, source)
					end
				else
					warn(`Failed to inherit '{index}', index not found`)
				end
			end
		end,
		Metatable = function(table1: any, table2: any)
			if table1 then
				return if table2 then setmetatable(table1, table2) else setmetatable({}, table1)
			else
				table1 = {} table1.__index = table1
				return table1
			end
		end,
	},
}

task.defer(function()
	-- Wait for all script threads to end
	local thread = next(threads)
	while thread do
		if coroutine.status(thread) == "dead" then threads[thread] = nil else task.wait() end
		thread = next(threads)
	end
	threads = nil
	
	-- Apply all scheduled inheritances
	local function Inherit(destination, inheritance)
		inheritances[destination] = nil
		for index, data in inheritance do
			local source = exports[data[1]] or globals[data[1]]
			if source then
				if data[2] == "Clone" then
					local inheritance = inheritances[source]
					if inheritance then Inherit(source, inheritance) end
					local indices = data[3]
					if #indices == 0 then
						for index, value in source do if destination[index] == nil then destination[index] = value end end
					else
						for index, value in indices do if destination[value] == nil then destination[value] = source[value] end end
					end
				else
					setmetatable(destination, source)
				end
			else
				warn(`Failed to inherit '{data[1]}', index not found`)
			end
		end
	end
	for destination, inheritance in inheritances do Inherit(destination, inheritance) end
	inheritances = nil
	
	-- Defer all initialize functions/threads
	for index, functionOrThread in initializes do initializeThreads[task.defer(functionOrThread)] = true end
	initializes = nil
	
	task.defer(function()
		-- Wait for all initialize threads to end
		local thread = next(initializeThreads)
		while thread do
			if coroutine.status(thread) == "dead" then initializeThreads[thread] = nil else task.wait() end
			thread = next(initializeThreads)
		end
		initializeThreads = nil

		-- Defer all start functions/threads
		for index, functionOrThread in starts do task.defer(functionOrThread) end
		starts = nil
	end)
end)

return setmetatable(globals, metatable)

Other Projects

Infinite Terrain
Packet
Suphi’s DataStore Module
Global Framework
Infinite Scripter
Mesh Editor
Quick Math Calculator
Toggle Block Comment
Toggle Decomposition Geometry
Tag Explorer
Suphi’s Linked List Module
Suphi’s Hybrid Linked List Module
Suphi’s RemoteFunction Module
Robux Converter

74 Likes

Never knew block comments could be used like this! Cant wait to check it out.

1 Like

Normally block comments don’t do anything but with the help of the plugin we can make some cool things happen :slight_smile:

if you run into any problems while checking it out report back and ill do my best to help

2 Likes

I’ve been enjoying using shared lately but it does lack autocomplete, I’m definitely gonna look into this because requiring modules, keep autocomplete and not having cyclic dependency issues is quite the headache.

eliminating cyclic modules is pretty bad, the error exists for a reason lmao

Why is it bad and what is the reason?

the modules end up being tightly coupled together which is a software nightmare, and it’s just complete violation of any code design principals

this isn’t really a framework per se, its more a global variable/type bootstrapper

Why is it bad to violate these coding design principles?

In order for something to be considered a framework what does it need to do/have?

15 Likes

Hello, this module is great but; Is there a way to update the variables and let other scripts access the updated variable? currently I have a whole table of variables and I’m setting elements of the table to a different value, but it doesn’t update on other scripts.

(sorry If I sound dumb, never used global variables like these before)

do you mind sharing your code so i can see what your doing

but if you make a table global all scripts will have access to that exact same table and any changes made to the table should be reflected to all scripts so i’m not really sure how you managed to not make it work correctly

1 Like

I love how their whole argument is “It violates some rule someone invented”, and then don’t question why it is bad.

4 Likes

I’m working on a custom physics character controller, I’m using the Global Framework module to store variables like velocity, position, etc…
I have a main module that updates the state machine and handles all client-related things; I’m also setting the Velocity of the character to the velocity value inside of the Global Framework.
StateMachine → Ground module:

--> runs every physics frame
Global.ClientShared.Velocity = Global.ClientShared.WishDirection
--> wishdirection is being set in ControlModule, works normally

print(Global.ClientShared.Velocity) --> output: the velocity vector (not Vector3.zero)

ControlModule (main module):

local Global = require(Libraries.Global);

--> runs every physics frame
Global.ClientShared.WishDirection = InputDirectionLocal --> works
print(Global.ClientShared.Velocity) --> Vector3.zero

LinearVelocity.VectorVelocity = Global.ClientShared.Velocity --> Vector3.zero

Global Script (RunContext to Client):

--[[type ClientShared = {.. Velocity: Vector3; ..}]]
Global('ClientShared', {.. Velocity = Vector3.zero; ..})

I’ve looked through everything, didn’t seem to see anything wrong that I did.

is this all happening on the client side

because global framework does not replicate over the network

also if your using the plugin then the plugin wont be able to understand your require()

1 Like

yeah, it’s all clientsided

the statemachine is able to read updated values from the controlmodule but the controlmodule isnt reading updates from the statemachine.

by plugin i meant like the first part of your video (the Plugin section)

are they all running on the same actor?

yes but you only need the plugin if you want help with type checking if your not using type checking then you dont need the plugin

I’m not using an actor – here’s the structure (the global module is the framework, the script under it just defines the variables
image
the script inside:

--[[
	type ClientShared = {
		Position: Vector3,
		WishDirection: Vector3,
		Velocity: Vector3,

		LinearVelocity: LinearVelocity,
		AlignOrientation: AlignOrientation,
		Character: Model,

		IgnoreParams: RaycastParams,
	}
]]

local Global = require(script.Parent)

local ClientShared: Global.ClientShared = Global('ClientShared', {
	Position = Vector3.new(0, 4, 0),
	WishDirection = Vector3.zero,
	Velocity = Vector3.zero,

	LinearVelocity = nil,
	AlignOrientation = nil,
	Character = nil,

	IgnoreParams = RaycastParams.new(),
}) :: Global.ClientShared

the ControlModule is inside of the PlayerModule inside of StarterPlayerScripts.

edit* yeah, i’m using the plugin for intellisense

1 Like

hum strange with the code you have shown everything looks like it should work i don’t see any problems with it

Are you setting velocity back to zero after physics step?

3 Likes

OH MY GOD. I just realised i kept changing it back to 0 for resimulating the movement when getting a snapshot from the server :sob:

thank you for making me realize that :pray: :sob: sorry for my stupidity…!!

1 Like

i’m happy you solved your problem :grin:

4 Likes

Is it an intended feature that this only works for scripts inside ServerScriptService, or am I doing something wrong?

1 Like