I’ve learned a lot about Roblox game development over the past four years, and I think it’s time to learn how to write efficient code.
However, writing efficient code is so difficult and complex; there seems to be too much to learn (e.g., instance pooling, thread pooling, entity component system).
I’m also not sure how to structure my scripts. There are many different ways to do it.
How can I improve my code efficiency and structure it well?
What I am doing rn is that on the serverside I have a playerController and a gameController, in wich you can call services such as dataService or roundService. I then load the modules with a single serverscript.
On the clientside you would have a module that handles the ui related stuff and effects with remotevents or remotefunctions.
Data is handled using ProfileStore and attributes, for cleanup I use Throve and for signals you could use Signal+ or GoodSignal.
Most of the code you will ever write doesn’t need to be efficient. Don’t fall into the trap of over optimizing your own code to the point where it becomes unreadable and hard to modify. If anything I think the skill you should develop first is the ability to figure out which segments of code are worth to optimize in the first place, aka which segments of code are responsible for most of the in-game lag. More often than not, lag issues in a game come from very few lines of code that must do a very heavy job, if you really want to optimize, go for those.
I would also recommend against optimizing during the early stages of development, for the same reason I advice against focusing on anti-cheat during early stages. Before you try to make a game work well or be very secure, you first need a game. Something with very defined mechanics and rules. If you focus on matters like this on early development, those mechanics and rules are very likely to change over and over again, which will force you to write the same code over and over again.
ECS is basically functional programming with a shared mutable state.
Nothing mystical. Just tables indexed by entities and pure logic functions acting on them.
Example ECS-ish pattern:
local Players = game:GetService("Players")
local Component_Health:{[Player]:number}={}
local function PlayerAdded(Plr:Player):()
Component_Health[Plr]=100
Plr.CharacterAdded:Connect(function(char):()
local humanoid = char:WaitForChild("Humanoid") :: Humanoid
Component_Health[Plr]=humanoid.Health
end)
end
Players.PlayerRemoving:Connect(function(plr):()
Component_Health[plr]=nil
end)
Players.PlayerAdded:Connect(PlayerAdded)
for i,v in Players:GetPlayers() do PlayerAdded(v) end
Efficient code = code that ONLY does what it needs to do.
No ambiguity. No wrapper circus. No “professional-looking” abstraction slop.
Just:
predictable
linear
optimized logic
as few layers as possible between your idea and the bytecode
Don’t drown yourself in unnecessary frameworks. Don’t use React-tier nonsense. Write what you understand, and optimize as you go. Your goal is clean bytecode, not “my architecture looks senior-qualified.”
If you want to understand efficiency, you MUST understand what your Luau turns into. Here’s my bytecode tutorial:
Also: avoid the “single script architecture” and avoid fragmentation for no reason. Luau has no linker - don’t spam ModuleScripts unless they serve a clear purpose.
I thought he wanted to know how he should srructure his scripts …
(and with 4 years of experience I don’t think the OP has a problem understanding with what I wrote)
If your logic is singular, keep it in a singular script.
Splitting it for no reason only scatters functions across the VM, forcing it to switch context unnecessarily. Unlike compiled languages like C++, Luau can’t precompile a ModuleScript and a Script into one function, so pointless splits hurt performance.
Keep related logic unified, ModuleScripts only exist to solve a real problem, not to flex abstractions.
I understand it to some extent, but it doesn’t quite resonate with me; could you show me the architecture of your game? I think it would be motivating to see a good example.
Your primary goal should be: How can I achieve my goal in solving X problem and releasing my game?, instead of How to write "efficient code".
There are going to be people who will try to drag you into the rabbit hole of micro-optimization that doesn’t matter in 99.99% of cases in production runtime.
You don’t have to optimize systems that are not restricted by performance bottlenecks, and it’s almost all the code you’ll write.
You don’t have to fight for 2 nanoseconds of execution time, you don’t have to fight for 1 megabyte memory usage. Your code WOULDN’T blow up if you wouldn’t be on your toes 100% of the work time, being careful about not writing “bad” code.
Just write the code that solves your use case, and do not listen to people who will judge you for wasting 0.1 nanosecond of CPU time.
A released game with “inefficient” code is better than a game stuck in development hell, but has “very well optimized bytecode mega super pro 9000”
You should stop trolling and showing people wrong ECS examples, and instead link actual resources that might help people understand ECS and get into it. Saving data into a table doesn’t equal ECS.
Some ACTUAL ECS resources, made by one of the Anime Vanguards developers:
That’s not really how it works. In a dynamic language without a preprocessor or linker, this is the only way to implement ECS without fighting the VM. You’d be better off understanding why I made these decisions instead of trying to argue with me.
Those examples you showed? Just wrappers and metatable circus code all over again. I ran benchmarks and studied the bytecode to see what the VM actually executes.
Cut the fluff, eat the bug Glory to the New Luau order
We will patiently wait for you to write a better ECS library without “wrappers and metatable circus”, it’s going to be an immense contribution to the developing community.
I am a huge believer in non-optimized scripting when building a program, a more “get it rolling and refine” kind of approach. Getting your programs to work this way is a blessing later when you come back and optimize them during refinement. Now you know your concept works, and every refinement just adds to how well it will perform.
Starting out this way tends to come with drawbacks and debugging nightmares. This may just be me, but I like to know that what I’m working on works before I start refining.
As for the real answer to your question, that comes with time, as all things programming do.
ignoring ECS, you should also mention that writing with a consistent format helps with maintaining the efficient code
local Players = game:GetService("Players")
local Component_Health: {[Player]: number} = {} -- Add spacing. This isn't one word.
-- I use PascalCase and avoid abbreviations; Plr -> Player.
local function PlayerAdded(Player: Player): ()
-- Stating the function returns nothing isn't necessary here (default),
-- but i'll leave it.
Component_Health[Player] = 100
Player.CharacterAdded:Connect(function(Character): ()
local Humanoid = Character:WaitForChild("Humanoid") :: Humanoid
Component_Health[Player] = Humanoid.Health
end)
end
local function PlayerRemoving(Player: Player): ()
Component_Health[Player] = nil
end
Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerRemoving)
-- Index isn't used; v -> Player.
-- I like one-liners, but don't force it.
for _, Player in (Players:GetPlayers()) do
PlayerAdded(Player)
end
note: i’m really just nitpicking because the original code block was cheaping out on spacing. find a good format you like and stick with it (everywhere)
Many beginners have a problem with “functions overhead” this is when one function calls another function and this another function calls another function. this problem is partially covered with inline calculations,
for example:
-- BAD EXAMPLE:
-- ye snake_case cuz ima rust programmer also
local function calculate_damage(atack: number, defense: number)
return atack * 2 - defense
end
local total_damage = 0
for i = 1, 100_000 do
total_damage = total_damage + calculate_damage(4, 2)
end
print(total_damage) -- <- bad perfomance
-- GOOD EXAMPLE WITH `INLINE` calculations
local total_damage = 0
for i = 1, 100_000 do
total_damage = total_damage + (10 * 2 - 5) -- <- GOOD OPTIMIZATION
end
The point of inline calculations is to remove unnecessary intermediaries in the form of functions
There isn’t an “ECS library.”
ECS is a paradigm - a way to structure data and logic.
What you’re calling “ECS libraries” are just wrappers stacked on wrappers, and every wrapper is overhead.
Luau doesn’t have macros, a linker, or compile-time specialization like C/C++, so a true zero-cost ECS cannot exist as a library.
The moment you abstract it, you’re already losing the whole point.
If you want real ECS, you write it directly.
If you want wrappers… that’s just OOP with a funny hat.