Hey folks! Looks like you stumbled across an old post of mine. Be weary that some information may be outdated!
Original Post
Hey there!
Even after being on Roblox for a decade now, I struggle with fighting performance problems with poor memory management chewing away at framerate and internet response time.
It seems that some of my games, especially the ones with my weapon system, has a TON of issues keeping that 60 FPS in the stats.
Which of these cause lag? Which of these should I worry most about?
-
coroutine
(should I usespawn
. Or should I usewrap
instead ofresume(create)
?) - Weak tables (for internal modules and other data)
- Module scripts
- Storing Roblox metamethods, functions, and instances as variables in the script (see Figure 1)
- Use of
pcall
- Usage of LuaU syntax (e.g.
local bruh:number = 5
) - Usage of
delay
- Large scripts (800-2000 average-sized lines of code)
- Instance connections such as
Died
,Changed
, andHeartbeat
. - Usage of JSON encoded strings rather than tables.
- Constant springing calculation (see Figure 2)
- Usage of a custom raycast function (see Figure 3)
- Constant calling of bobbing calculation (see Figure 4)
- Usage of constraints on client vs on server. Both under workspace.
- Using
Tween:Create():Play()
instead of pre-creating tweens. - Using
SetPrimaryPartCFrame
vs usingPrimaryPart.CFrame
- Using
LocalTransparencyModifier
vsTransparency
- Embedding tables instead of using a variable (see Figure 5)
- Using
Instance.new(_,parent)
rather thanInstance.new(_).Parent
- Using
FindFirstChild
per heartbeat. - Puttings things like entire maps on client, vs on server.
Those are things I could think of, but if there are common mistakes that cause things to not be garbage collected I would love to hear.
Figure 1
local wfc = game.WaitForChild
local ffc = game.FindFirstChild
local ffcoc = game.FindFirstChildOfClass
local getch = game.GetChildren
--// Services
local UserInput = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local TextService = game:GetService("TextService")
local Tween = game:GetService("TweenService")
local RepStore = game:GetService("ReplicatedStorage")
local Context = game:GetService("ContextActionService")
local events = wfc(RepStore,"Events")
local models = wfc(RepStore,"GunModels")
local modules = wfc(RepStore,"GunModules")
local assets = wfc(RepStore,"Assets")
local tools = wfc(RepStore,"Tools")
--// Variables
local cf = CFrame.new
local ang = CFrame.Angles
local v3 = Vector3.new
local lerpv3 = v3().lerp
local lerpcf = cf().lerp
local v2 = Vector2.new
local c3 = Color3.new
local rgb = Color3.fromRGB
local brc = BrickColor.new
local ud2 = UDim2.new
local sin,cos,tan = math.sin,math.cos,math.sin
local asin,acos,atan = math.asin,math.acos,math.atan
local pi,tpi,hpi = math.pi,math.pi*2,math.pi/2
local atan2 = math.atan2
local sqrt = math.sqrt
local abs = math.abs
local random = math.random
local floor,ceil,clamp = math.floor,math.ceil,math.clamp
local rad,deg = math.rad,math.deg
local rdm = math.random
Figure 2
-- This is a springing module, with springs being calculated a couple times every frame.
local spring = {}
do --// Spring
local tick = tick
local cos = math.cos
local sin = math.sin
local e = 2.718281828459045
local function getposvel(s, d, p0, c1, c2, t0)
local t = tick() - t0
if d >= 1 then
return (c1 + c2 * s * t) / e ^ (s * t) + p0, (c2 * s * (1 - t) - c1) / e ^ (s * t)
else
local h = (1 - d * d) ^ 0.5
return (c1 * cos(s * h * t) + c2 * sin(s * h * t)) / e ^ (s * d * t) + p0, s * ((h * c2 - d * c1) * cos(s * h * t) - (h * c1 + d * c1) * sin(s * h * t)) / e ^ (s * d * t)
end
end
function spring.new(initial, s, d)
local self = {}
local s = s or 15
local d = d or 0.5
local p0 = initial or 0
local c1 = 0 * p0
local c2 = 0 * p0
local t0 = tick()
function self:impulse(a)
local p, v = getposvel(s, d, p0, c1, c2, t0)
t0 = tick()
c1 = p
if d >= 1 then
c2 = c1 + (v + a) / s
else
local h = (1 - d * d) ^ 0.5
c2 = d / h * c1 + (v + a) / (s * h)
end
end
local meta = {}
function meta.__index(table, index)
if index == "position" or index == "p" then
local p, v = getposvel(s, d, p0, c1, c2, t0)
return p
elseif index == "velocity" or index == "v" then
local p, v = getposvel(s, d, p0, c1, c2, t0)
return v
elseif index == "dampen" or index == "d" then
return d
elseif index == "speed" or index == "s" then
return s
end
end
function meta.__newindex(table, index, value)
if index == "dampen" or index == "d" then
d = value
elseif index == "speed" or index == "s" then
s = value
elseif index == "target" or index == "t" then
local p, v = getposvel(s, d, p0, c1, c2, t0)
t0 = tick()
p0 = value
c1 = p - p0
if d >= 1 then
c2 = c1 + v / s
else
local h = (1 - d * d) ^ 0.5
c2 = d / h * c1 + v / (s * h)
end
end
end
return setmetatable(self, meta)
end
end
return spring
Figure 3
-- This is a raycast module. When a bullet is fired, this is called every single Heartbeat frame
local ray = {}
local ffc = game.FindFirstChild
local ffcoc = game.FindFirstChildOfClass
local cf = CFrame.new
do --// Ray
ray.new = function(a, b, i)
local d = (a - b).magnitude + 0.01
local hit, pos, norm
local function go(st, lv, i2)
local ray = Ray.new(st, lv)
local ig = i2
local h, p, n = workspace:FindPartOnRayWithIgnoreList(ray, i2, false, true)
if h then
if h.Name == "bullet" or h.Name == "serverbullet" then
return
end
if ffcoc(h.Parent, "Humanoid") then
hit, pos, norm = h, p, n
elseif h.CanCollide == false or h.Name == "HumanoidRootPart" or h.Transparency == 1 then
if ffc(h.Parent, "Humanoid") then
hit, pos, norm = h, p, n
else
ig[#ig + 1] = h
go(p, lv, ig)
end
else
hit, pos, norm = h, p, n
end
else
hit, pos, norm = h, p, n
end
end
go(a, cf(a, b).lookVector * d, i)
return hit, pos, norm
end
end
return ray
Figure 4
-- Another function similar to this exists in the code, and both are called per frame.
local function cambob(infl)
if infl == nil then
infl = 1
end
local s0,d0,d1,d2,d3 = 10,12,15,29*40,14*40
local t,s = tick()*infl,s0/2
local c = cf(sin(t*s)/d0,cos(t*s0)/d1,0)
local a = ang(sin(t*s0)/d2*infl^2,0,sin(t*s)/d3*infl^2)
return c*a
end
Figure 5
bullet.Color = ({
white = Color3.fromRGB(255,255,255);
red = Color3.fromRGB(255,0,0);
blue = Color3.fromRGB(0,80,255);
purple = Color3.fromRGB(179,0,255);
green = Color3.fromRGB(0,255,0);
pink = Color3.fromRGB(255,0,255);
black = Color3.fromRGB(0,0,0);
yellow = Color3.fromRGB(255,255,0);
orange = Color3.fromRGB(255,136,0);
turquoise = Color3.fromRGB(0,255,255);
})[player.stats.bcolors.Value] or Color3.new(1,1,0)
These issues are on front-page and semi-popular games, such as No-Scope Sniping and Infinite Autocorrect.
Thanks so much for your help!