Weird memory spikes that occur shortly after the game starts

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?


2 Likes

Show the code.

1 Like

The code of the AI right? that is a pretty vague question

1 Like

Not just the AI, but also the code where you spawn the enemies.

1 Like

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?

1 Like

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

1 Like

Could you show your attack script?

1 Like

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

1 Like

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.