Firework, a general purpose framework

Maybe, but all of the information you would need is in this post. It’s a very simple framework that gives most of the power to individual modules.

@kiloe2 Yeah, it’s a lot like Knit, but it’s a lot lighter. It was designed to solve problems I encountered while developing various projects.

2 Likes

Update: Added an assert to the Get method to catch some mistakes.

function Framework.Get(name)
	assert(Framework.Modules[name], name .. " is not registered")
	
	return Framework.Modules[name]
end
1 Like

Man I really love these frameworks that let you step and run code in order, ever since I found about these types of structures for writing games thanks to chickynoid, I dont think I could ever stop using it

So this framework goes to my list of cool frameworks (Canary engine, Knit, Firework)

2 Likes

Submodules Update

Added support for submodules! You can now register modules as part of other modules.

To add submodules, include a Submodules table:
(the keyword for this can be changed in settings)

local Player = {}

Player.Submodules = {
	script.DataStoreHandler,
	script.PurchaseHandler,
	script.Character
}

It uses the same register system as regular modules. The load order is done module first, then submodules. Finally, the submodules table is replaced with a dictionary.

If you want a more advanced way to find these submodules, you can provide a callback instead of a table.

local EffectSystem = {}

EffectSystem.Submodules = function()
	local submodules = {}

	for _, module in pairs(script:GetChildren()) do
		table.insert(submodules, module)
	end

	return submodules
end

To get submodules, the Framework.Get method has been upgraded! You can now provide a chain of submodules after the initial module name.

Player.Framework.Get("Player", "Character")

If you want to perform a deep search, you can keep adding arguments.
(I haven’t extensively tested this, let me know if this has issues)

CombatSystem.Framework.Get("CombatSystem", "Finders", "GetPlayers")

Register and RegisterRuntime have also been upgraded! You can now pass a table to register the given module into. This is optional only for RegisterRuntime, where no table will default to Framework.Modules as usual.

Framework.RegisterRuntime(module, name, modules)

For example:

Character.Framework.RegisterRuntime(script:WaitForChild("Local"), nil, Character.Types)

Some other quality of life changes were made, including more descriptive errors. Let me know if you run into any problems!

1 Like

May I know how Bindable events work with this framework?

There is no added functionality with bindables in this framework, just remotes. They can be used the same as usual.

1 Like

I am a little confused at how you would get other modules within one module and then use its functions throughout, are you able to provide a more detailed example to that than the one you gave above?

Also, I wanted to ask if there is a reliable way I could get the Player when I need to on the server side modules, or do I just have to use this?

 game.Players.PlayerAdded:Connect(function(player)
	player.ChracterAdded:Connect(function(char)
	end)
end)

In any module that has been registered, you gain access to the framework. For example, if you had a module named “PlayerHandler” like so, here are some ways to access other modules:

local PlayerHandler = {}

function PlayerHandler.Initialize()
    -- we know the module has been registered now, since initialize runs afterwards
    PlayerHandler.DataStoreHandler = PlayerHandler.Framework.Get("DataStoreHandler")
end

function PlayerHandler.SomeOtherFunction()
    -- As long as we call this after PlayerHandler has been initialized,
    -- we can use PlayerHandler.Framework to reference the framework and get
    -- other modules. Note that DataStoreHandler must also be registered.
    local DataStoreHandler = PlayerHandler.Framework.Get("DataStoreHandler")

    -- now we have two ways of accessing the DataStoreHandler
    -- one from Initialize:
    PlayerHandler.DataStoreHandler.Save()
    -- and one from this function:
    DataStoreHandler.Save()
end

return PlayerHandler

Now that we have a reference to DataStoreHandler, we can make calls to it as if we required it. For example: PlayerHandler.DataStoreHandler.Save()

For completeness, here’s an example of initializing the framework with these modules:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Framework = require(ReplicatedStorage.Framework)

Framework.Initialize(
	script.PlayerHandler,
	script.DataStoreHandler
)

The load order here doesn’t have any significant impact on when modules are registered. However, it does determine the order in which the initialize functions are called. Just keep that in mind.

I like to make these Get calls in the initialize function, because we can assume everything else in the framework has already been registered. This is not the case when using RegisterRuntime, which is why I did the submodules update.

For the player, yes you will have to set up your own player handler. This framework is light and does not provide inbuilt systems for things like that as of now. It’s just meant to augment and automate some backend work.

1 Like

Alright I get it now, thank you!

The problem with this framework is Intellisense same as knit getting module by name is not good at all as it defeats Intellisense overall good framework I love it and I hope it improves to the limitless!

The purpose of switching to names is to do away with parent chains, or just script paths in general. It’s a tradeoff, but one I’ll take over having to do something like this over and over:

local EffectSystem = require(script.Parent.Parent.Parent.EffectSystem)

Another issue with this method is that you have to change the path depending on what script is using it. If you wanted the example EffectSystem in another module, you might have to modify that path each time you copy and paste it. With names, you can copy and paste the Framework.Get method to any registered module.

But yeah, no intellisense. Maybe one day we’ll have built-in AI that can figure it out at a glance.

1 Like

Remote Queue Update

I’ve made a small change that queues up server-to-client remotes while the framework is loading. I ran into an issue where the server was telling the client to do things that weren’t loaded yet, so I added this setting as a remedy.

QueueRemotesDuringLoading = true

In addition to this, you can now check if the framework is loaded or wait for it to finish loading.

-- to wait for loading:
Framework.WaitForLoaded()

-- to check if loading has completed:
if Framework.IsLoaded then
	-- some code here
end

Note that IsLoaded is not a function, but a boolean property. That’s all for now!

It’s a nice framework and it’s lightweight.

  • Has no benefits to using anything else
  1. No Intellisense support, a key major role in 99% of workflows
  2. Slow remotes, uses default remotes which are terrible at packet management

Should you use it? Probably not, would only recommend in small projects.

Also may I add this is very redundant, why wait for it to load and check if it’s loaded? If you waited for it then you can guarantee its loaded.

Should also be a Future to remove the fact of auto yielding (yucky)

  • Has no benefits to using anything else

Maybe? There are a few features here like automatic submodules that aren’t included in any frameworks I’ve come across before. It’s relatively new and being expanded upon frequently. Feel free to spark some ideas for me.

  1. No Intellisense support, a key major role in 99% of workflows

I usually end up looking at the functions I want to use before using them anyway, but yes this is a slowdown. You don’t get to see any type info you might have set. I don’t have a solution for this yet.

  1. Slow remotes, uses default remotes which are terrible at packet management

What other option do you have? Remotes are guaranteed to go through in the desired order and are never dropped. I’m curious what you mean by this.

Should you use it? Probably not, would only recommend in small projects.

Up to you. This is just my workflow, and I actively use it on big projects over years of development. To me, it seems like this framework lacks some things you want to use in your workflow.

Also may I add this is very redundant, why wait for it to load and check if it’s loaded? If you waited for it then you can guarantee its loaded.

This is just example code to show how to use it. This was a bonus feature from making remote calls wait until the corresponding remote handlers were indexed and findable in the framework. Might be useful for something that is not part of the framework.

Should also be a Future to remove the fact of auto yielding (yucky)

If you mean the remotes waiting for the framework to load, yes this is a setting. In practice, you will find that you want this on. Any remotes fired to the client during loading will error.

Overall, I don’t mean to criticize you for reasonable points. You bring up some issues that I want to look into, and I appreciate your time to judge what I’ve got here.

1 Like

UnreliableRemoteEvent Support

With the introduction of the UnreliableRemoteEvent, I have added new syntax for marking remotes as unreliable. They are handled separately from normal remotes as to prevent functions marked as unreliable from being called reliably, and vice-versa.

To mark a function as unreliable, the new default keyword for this is “Unreliable” and is modifiable in the Settings module. Here’s an example:

local Test = {}
Test.Unreliable = {} -- notice that we need to create this table manually

function Test.Initialize()
	-- dummy function
end

function Test.Unreliable.Print(player, text) -- can only be called unreliably
	print(text)
end

return Test

If we assume the above is server code, we can call it from the client using the new FireUnreliable

Test.Framework.FireUnreliable("Test", "Print", "hello")

This syntax works on both the client and the server. However, the server must still provide a player argument for firing remotes. The server also has access to a few new unreliable methods:

Framework.FireAllClientsUnreliable(moduleName, remoteName, ...)
Framework.FireAllExceptUnreliable(player, moduleName, remoteName, ...)

These are variations of FireAllClients and FireAllExcept and function the same way, just using unreliable remotes. I am excited to see the applications of this new feature!

1 Like

FireAllClientsNearby

Today I added a new function out of necessity. You can now call Framework.FireAllClientsNearby from the server, and you can also provide a callback to check distance if you need. This was added to try and reduce network usage for players far from an event that don’t necessarily need to be replicated to.

function Framework.FireAllClientsNearby(position, radius, callback, moduleName, remoteName, ...)
	callback = callback or function(player, maxDistance)
		if not player.Character or not player.Character.PrimaryPart then
			return
		end
		
		local distance = (player.Character.PrimaryPart.Position - position).Magnitude
		
		return distance < maxDistance
	end
	
	for _, player in pairs(Players:GetPlayers()) do
		if callback(player, radius) then
			Framework.Fire(moduleName, remoteName, player, ...)
		end
	end
end

In code, the default callback just tests character primary part distance from the given position. If you want to use the default, just provide nil to the function. The rest of the arguments are passed to the usual Framework.Fire method.

2 Likes

Great framework! Recently started using this and practicing creating projects with it! :happy1:

1 Like
-- Services
local CollectionService = game:GetService("CollectionService")
local Rep = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local SSS = game:GetService("ServerScriptService")
local RS = game:GetService("RunService")
local UIS = game:GetService("UserInputService")
local TS = game:GetService("TweenService")
local Players = game:GetService("Players")

-- Important Variables and Modules
local Framework = require(Rep.Framework)
local modules = {}
local moduleCount = 0
local allLoaded = false


for _, mod in script:GetChildren() do
	if mod:IsA("ModuleScript") then
		local r = require(mod)
		modules[mod.Name] = r
		moduleCount += 1
		Framework.Initialize(script[mod.Name])
		print(script[mod.Name])
		print("Client Firework Framework - Loaded module: ", mod.Name)
	end
end

while not allLoaded do
	allLoaded = true
	for _, mod in pairs(modules) do
		if not Framework.Modules[mod] then
			allLoaded = false
			warn("Waiting for module to be loaded: ", mod.Name)
			Framework.Initialize(script[mod.Name])
			break
		end
	end
	task.wait(1)
end

Framework.WaitForLoaded()
if Framework.IsLoaded then
	warn("Client Firework Framework - Initialization complete. Modules loaded: ", moduleCount)
	if workspace.Not and workspace.Not.Prox then
		workspace.Not.Prox.Triggered:Connect(function()
			local Notify = Framework.Get("Notifications")
			Notify.CreateNotification("Hoi", Color3.fromRGB(255, 255, 255), "MessageBound")
		end)
	else
		warn("womp womp")
	end
end

I noticed that specifically one module on my client loader most times doesn’t initialize? I’m not sure if I’m doing something wrong, but it doesn’t register it.

Framework.Initialize is intended to be used with multiple modules at once. If you want to register them sequentially, I would recommend Framework.RegisterRuntime instead.

Fixed the issue, turns out the allLoaded = false was creating sort of an infinite loop even though it was initialized!