I’m making a game of where you defend a base from waves of enemies
however i saw that the game experiences memory leakages which present as spikes in memory usage, this occurs randomly but usually after the game begins
the server memory will start at 900 mb, then when enemies start spawning the memory goes up to 1000 - 1100 mb but then out of nowhere for no apparent reason, the memory spikes to 2000+ mb and then after a while into the game like when i’m at least wave 5, severe lag starts occuring until most of the enemies are dead. It makes the game almost unplayable.
and i can’t find the cause at all, i thought it was the ai so i revamped the entire enemy ai code which uses pathfinding which lessened the leakages but they are still present.
any helP?
Show the code.
The code of the AI right? that is a pretty vague question
Not just the AI, but also the code where you spawn the enemies.
I would recommend using the new VM Luau tool
ok i will get to that
i’ll just send a model for the AI
i don’t know if i should send the full code though so i’ll send a bit of it
Spawner
This is the code that spawns the enemies
local function ShowCurrentWave()
CountDownRemote:FireAllClients(false)
task.wait(.65)
CountDownRemote:FireAllClients(true, "Wave: " .. Wave)
end
local function countdown(Starting)
if Starting then
for i = 0, 20 do
task.wait(1)
CountDownRemote:FireAllClients(true, "Round Starts in " .. tostring(20 - i))
end
CountDownRemote:FireAllClients(true, "BEGIN", Color3.new(0, 1, 0))
task.delay(1, CountDownRemote.FireAllClients, CountDownRemote, false)
else
for i = 0, 10 do
task.wait(1)
CountDownRemote:FireAllClients(true, "Boss is approaching in " .. tostring(10 - i), Color3.new(1, 0 ,0))
end
CountDownRemote:FireAllClients(false)
end
end
local function SpawnEnemies()
while task.wait() do
local Limit = math.clamp(math.ceil(AmountNeededToProgress / 2), 0, 50)
local Maximum = AmountNeededToProgress + 10
if EnemiesKilled.Value >= AmountNeededToProgress then
continue
end
if not Monolith or Monolith:FindFirstChild("Humanoid") and MonolithHum:GetState() == Enum.HumanoidStateType.Dead then
continue
end
if not Active then
continue
end
local Interval = math.random(3, 6)/5
task.wait(Interval)
local EnemyToUse = EnemiesToUse[math.random(#EnemiesToUse)]
if SubBossPresent and EnemyToUse:FindFirstChild("Boss") then
continue
end
if TotalEnemies < Maximum and EnemiesLeft.Value < Limit and EnemyToUse:FindFirstChild("Wave") and Wave >= EnemyToUse:FindFirstChild("Wave").Value then
local RandSpawner = EnemySpawners:GetChildren()[math.random(#EnemySpawners:GetChildren())]
local Enemy:Model = EnemyToUse:Clone()
Enemy.Parent = workspace
Enemy:MoveTo(RandSpawner.Position + RandSpawner.CFrame.RightVector * math.random(-100, 100))
local ClonedScript = ScriptToClone:Clone()
ClonedScript.Enabled = true
ClonedScript.Hostile.Value = true
ClonedScript.Parent = Enemy
EnemiesLeft.Value += 1
TotalEnemies += 1
if Enemy:FindFirstChild("Boss") then
SubBossPresent = true
Enemy.Boss.Died:Connect(function()
SubBossPresent = false
Lives = math.clamp(Lives + 1, 0, 5)
end)
end
end
end
end
And This is the code that runs the spawners
local function WaveSystem()
Active = true
if not Monolith:IsDescendantOf(workspace) then
CreateMonolith(true)
end
AddObjectiveGuiToPlayers(true)
for i = Wave > 1 and Wave or 1, MaxWaves do
SubBossPresent = false
Wave = i
task.spawn(ShowCurrentWave)
EnemiesKilled.Value = 0
AmountNeededToProgress = 40 + (Wave * 10)
local Boss = Findboss(Wave)
repeat
task.wait()
until (EnemiesKilled.Value >= AmountNeededToProgress and not SubBossPresent) or MonolithHum:GetState() == Enum.HumanoidStateType.Dead
if Fail() then
break
end
if Boss then
countdown(false)
local NewBoss = Boss:Clone()
local BossHum = NewBoss:FindFirstChildOfClass("Humanoid")
local ClonedScript = ScriptToClone:Clone()
ClonedScript.Hostile.Value = true
ClonedScript.Enabled = true
ClonedScript.Parent = NewBoss
NewBoss.Parent = workspace
NewBoss:MoveTo(EnemySpawners:GetChildren()[math.random(#EnemySpawners:GetChildren())].Position)
repeat
task.wait()
until BossHum:GetState() == Enum.HumanoidStateType.Dead or MonolithHum:GetState() == Enum.HumanoidStateType.Dead
if BossHum:GetState() == Enum.HumanoidStateType.Dead then
Lives = math.clamp(Lives + 1, 0, 10)
end
end
if Fail() then
break
end
if not Boss then
CountDownRemote:FireAllClients(true, "Wave " .. Wave .. " Finished", Color3.new(0, 1, 0))
else
CountDownRemote:FireAllClients(true, Boss.Name .. " has been killed, You gain 1 Life, Moving on to next wave.", Color3.new(0, 1, 0))
end
task.wait(5)
TotalEnemies = 0
end
if Wave == MaxWaves and MonolithHum:GetState() ~= Enum.HumanoidStateType.Dead then
CountDownRemote:FireAllClients(true, "You've won, Starting again in 10 seconds", Color3.new(0, 1, 0))
end
task.wait(10)
Active = false
TotalEnemies = 0
Reset(Wave == MaxWaves and MonolithHum:GetState() ~= Enum.HumanoidStateType.Dead or Lives == 0)
AddObjectiveGuiToPlayers(false)
countdown(true)
CreateMonolith(false)
task.spawn(WaveSystem)
end
and for the script to clone
local IsDead = false
local MainScript = game:GetService("ServerScriptService"):WaitForChild("Main")
local NPCSkilled = MainScript:WaitForChild("NPCSkilled")
local Count = script.Hostile.Value and MainScript.EnemyCount or MainScript.AllyCount
script.Parent:FindFirstChildOfClass("Humanoid").Died:Wait()
IsDead = true
if script.Hostile.Value then
NPCSkilled.Value += 1
end
Count.Value -= 1
In case you wanna know, it just deletes the mob.
AI
Do you mean to disconnect this after it’s done?
I never really knew on how to use that tool but i’ll try it out
so here
The last 3 images is when the leak occurred
doesn’t it disconnect when the boss dies?
a died connection only fires once and plus mobs will destroy after a certain period so this should disconnect anyway
Could you show your attack script?
I can’t, attack script is a script present in lots of enemies and it can come in too much variety
i’m seeing that a module is being bad and i have the model of the module on the market so i’ll send the link necessary Module
I suggest not using this module, just skimming over it I have seen many mistakes/issues. Many instances are never cleaned up and events never disconnected. On top of that it is very vulnerable to exploits and has some cases which will cause it to yield forever. I suggest writing a more light weight version of it suited to your specific needs.
what and where are the mistakes and issues specifically?
the events are destroyed which is usually when the tween is destroyed, the tween is usually destroyed when the object is also destroyed
so it should be cleaned up already.
also how is it vulnerable to exploits and what kind of exploits is it vulnerable to?
and this module was made to be suited to my needs hence it’s ability to tween number sequences and models.
also how do you make it yield forever?
The server asks an arbitrary client for information it should already have on line 125, there is no type checking or validation and so the client can pass anything it would like directly into your script.
You have receive end events from every client without any validation, anyone can preemptively end any tween, on top of that its just unnecessary network traffic. The server already knows when the tween should end
On top of that because you are using InvokeClient, and invoking it from an arbitrary client, if that client were to join the server midway through a tween the server would yield until the client responded, but if the script isnt loaded yet, or the object is not replicated yet the client never respond.
I dont think the NewEvent object is ever removed unless you do that somewhere externally and all tweens are never removed from the TweensInUse table
Also I think line 151 does not do what you intend, as far as I know self = nil does not delete the object, just sets the reference to itself to nil
“The server asks an arbitrary client for information it should already have on line 125”
it’s meant to get the property of an object that is been tweened locally which can be used to get the current for example size of a currently tweening object.
so arbitrary huh, so what should i do then to get information from a client?
You have receive end events from every client without any validation
ok, i’ll consider adding a debounce to make it so it would fire only once from one client, but what do you mean by validation?
anyone can end a tween by just leaving right? what does that mean? and can that be fixed?.
and i thought that setting self to nil would allow the object to be gc’d
also i forgot to remove self from the tweensinuse table so thanks for letting me know
Arbitrary as in it asks whatever player is in the first index of GetPlayers() which to my knowledge is not ordered. Any exploiter can hook the function/remote and return anything they would like. If you aren’t handling the input in the script that interfaces with your module, it is a massive vulnerability.
You dont need to get the end event from the clients. The server knows when the tween starts and how long it should last, and so it knows when it ends.
Sorry, when I say anyone I mean any exploiter, as in the vulnerability is open to anyone but not everyone has the capabilities to abuse it. Any exploiter can fire your remote prematurely and have the server destroy the Object instance. This also may cause other clients to error as the object is removed mid tween although I think you check if the object exists on your client.