Remote event lag spikes

Hello everyone!

I want to make client-sided collecting system but each time remote event for reward fires it makes bigger and bigger lag spike and i don’t understand why

Client:

local touchedCubes = {}

local function onTouch(cube, cubeData)
	if touchedCubes[cube] then return end
	if not cube:IsA("BasePart") then return end
	local connection
	
	connection = cube.Touched:Connect(function(hit)
		touchedCubes[cube] = true
		AnimateCollect(cube)
		Remotes.CubeCollected:FireServer(hit, cubeData)
		game.Debris:AddItem(cube, 0.6)
		spawn(function()
			task.wait(0.5)
			touchedCubes[cube] = nil
		end)
		if connection then
			connection:Disconnect()
			connection = nil
		end
	end)
end

local function spawnCube()
	local cubeData = Remotes.CubeData:InvokeServer()
	if not cubeData then return end

	local cubeTemplate = CubesFolder:FindFirstChild(cubeData.Name)
	if not cubeTemplate then return end

	local cube = cubeTemplate:Clone()
	
	cube.Position = spawnArea.Position + Vector3.new(math.random(-spawnArea.Size.X/2 + 0.5, spawnArea.Size.X/2 - 0.5), spawnArea.Size.Y/2 + 0.5, math.random(-spawnArea.Size.Z/2 + 0.5, spawnArea.Size.Z/2 - 0.5))
	local x, y, z = string.match(cubeData.Value, "([^,]+), ([^,]+), ([^,]+)")
	if x and y and z then
		cube.Position = Vector3.new(tonumber(x), tonumber(y), tonumber(z))
		onTouch(cube, cubeData)
		cube.Parent = workspace:FindFirstChild(player.Name.."_Cubes")
	end
end

while playerCubesFolder do
	spawnCube()
end

Server:

remotes.CubeCollected.OnServerEvent:Connect(function(player, hit: BasePart, cubeData)
	local data = RS.Data:FindFirstChild(player.Name)
	cubeData:Destroy()
		
	data.Stats.Cubes.Value += CubesModule[cubeData.Name].Value
	data["Total Stats"]["Total Cubes"].Value += CubesModule[cubeData.Name].Value
	data.Stats.XP.Value += CubesModule[cubeData.Name].Value/10
end)



local function parseVector3(str)
	local x, y, z = string.match(str, "([^,]+), ([^,]+), ([^,]+)")
	if x and y and z then
		return Vector3.new(tonumber(x), tonumber(y), tonumber(z))
	end
	return nil
end

local function isPositionTaken(pos, takenPositions, tolerance)
	for _, taken in takenPositions do
		if (pos - taken).Magnitude <= tolerance then
			return true
		end
	end
	return false
end

local spawnDebounces = {}

remotes.CubeData.OnServerInvoke = function(player)
	print("function")
	local data = RS.Data:WaitForChild(player.Name)
	local spawnSpeed = data["Cube Stats"].Speed.Value
	local spawnLimit = data["Cube Stats"].Limit.Value
	
	if spawnDebounces[player.Name] or #data.Cubes:GetChildren() >= spawnLimit then return nil end

	spawnDebounces[player.Name] = true

	spawn(function()
		task.wait(1/spawnSpeed - 0.001)
		spawnDebounces[player.Name] = nil
	end)
	
	local chosedCube = CubesModule.ChooseCube()

	local cubeData = Instance.new("StringValue")
	cubeData.Name = chosedCube

	local randomX = math.random(-spawnArea.Size.X/2 + 0.5, spawnArea.Size.X/2 - 0.5)
	local randomZ = math.random(-spawnArea.Size.Z/2 + 0.5, spawnArea.Size.Z/2 - 0.5)
	local pos = spawnArea.Position + Vector3.new(randomX, spawnArea.Size.Y/2 + 0.5, randomZ)

	cubeData.Value = tostring(pos)
	cubeData.Parent = data.Cubes
	
	return cubeData

end

as you can see in the video lag spike is bigger and bigger with each cube touched (event fire)
without event for reward there is no lags but ofcourse i need to give players a reward any ideas on how to fix that?

making loop slower also doesn’t change anything and cubes are being collected from distance because i also want to include collect radius in the game which is already here created on different server script

pls do not accent on anything else only on what causing lag spike i’m just stuck because of that lag spike

i’ll appreciate any help!

1 Like

I would suggest updating the client code to this:


local cubeCap = 50
local activeCubes = {}

local function onTouch(cube, cubeData)
	if touchedCubes[cube] then return end
	if not cube:IsA("BasePart") then return end
	local connection
	
	connection = cube.Touched:Connect(function(hit)
		touchedCubes[cube] = true

		table.remove(activeCubes, table.find(activeCubes, cube))

		AnimateCollect(cube)
		Remotes.CubeCollected:FireServer(hit, cubeData)
		game.Debris:AddItem(cube, 0.6)
		spawn(function()
			task.wait(0.5)
			touchedCubes[cube] = nil
		end)
		if connection then
			connection:Disconnect()
			connection = nil
		end
	end)
end

local function spawnCube()

	if #activeCubes >= cubeCap then return end

	local cubeData = Remotes.CubeData:InvokeServer()
	if not cubeData then return end

	local cubeTemplate = CubesFolder:FindFirstChild(cubeData.Name)
	if not cubeTemplate then return end

	local cube = cubeTemplate:Clone()
	
	cube.Position = spawnArea.Position + Vector3.new(math.random(-spawnArea.Size.X/2 + 0.5, spawnArea.Size.X/2 - 0.5), spawnArea.Size.Y/2 + 0.5, math.random(-spawnArea.Size.Z/2 + 0.5, spawnArea.Size.Z/2 - 0.5))
	local x, y, z = string.match(cubeData.Value, "([^,]+), ([^,]+), ([^,]+)")
	if x and y and z then
		cube.Position = Vector3.new(tonumber(x), tonumber(y), tonumber(z))
		onTouch(cube, cubeData)
		cube.Parent = workspace:FindFirstChild(player.Name.."_Cubes")
	end

	table.insert(activeCubes, cube)
end

while playerCubesFolder do
	spawnCube()
	task.wait() -- add a little bit of a delay 
end

You can try this to see if it helps fix these problems:

  • I have added a slight delay to the while loop to prevent over-calling the function.
  • I have added a cap to the number of cubes that can exist (50).
1 Like

unfortunately it doesn’t changes anything

You can try change the bottom while loop to this:

task.spawn(function()
	while playerCubesFolder do
		if #activeCubes < cubeCap then
			spawnCube()
		end
		task.wait(0.2) -- slower but still reasonable
	end
end)

as i said in post changing while loop also doesn’t change anything and lag remove if remove CubeCollected event from touched connection but i don’t understand how it matters

It’s because you are overloading the server.

You are calling InvokeServer every single loop which can cause lag.

Every time a cube is touched (which can happen multiple times per frame) you are using FireServer and .Touched is a very spammy event.

You can try update your client code with this:

local player = game:GetService("Players").LocalPlayer

local cubeCap = 50
local activeCubes = {}
local cubeHandlerExists = {}

local function onTouch(cube, cubeData)
	if cubeHandlerExists[cube] then return end
	if not cube:IsA("BasePart") then return end
	local connection

	cubeHandlerExists[cube] = true
	
	connection = cube.Touched:Connect(function(hit)
		if touchedCubes[cube] then return end
		if not player.Character then return end
		if not hit:IsDescendantOf(player.Character) then return end

		touchedCubes[cube] = true

		table.remove(activeCubes, table.find(activeCubes, cube))

		AnimateCollect(cube)
		Remotes.CubeCollected:FireServer(hit, cubeData)
		game.Debris:AddItem(cube, 0.6)
		spawn(function()
			task.wait(0.5)
			touchedCubes[cube] = nil
		end)
		if connection then
			connection:Disconnect()
			connection = nil
		end
		cubeHandlerExists[cube] = nil
	end)
end

local function spawnCube()

	if #activeCubes >= cubeCap then return end

	local cubeData = Remotes.CubeData:InvokeServer()
	if not cubeData then return end

	local cubeTemplate = CubesFolder:FindFirstChild(cubeData.Name)
	if not cubeTemplate then return end

	local cube = cubeTemplate:Clone()
	
	cube.Position = spawnArea.Position + Vector3.new(math.random(-spawnArea.Size.X/2 + 0.5, spawnArea.Size.X/2 - 0.5), spawnArea.Size.Y/2 + 0.5, math.random(-spawnArea.Size.Z/2 + 0.5, spawnArea.Size.Z/2 - 0.5))
	local x, y, z = string.match(cubeData.Value, "([^,]+), ([^,]+), ([^,]+)")
	if x and y and z then
		cube.Position = Vector3.new(tonumber(x), tonumber(y), tonumber(z))
		onTouch(cube, cubeData)
		cube.Parent = workspace:FindFirstChild(player.Name.."_Cubes")
	end

	table.insert(activeCubes, cube)
end

task.spawn(function()
	while playerCubesFolder do
		if #activeCubes < cubeCap then
			spawnCube()
		end
		task.wait(0.2) -- slower but still reasonable
	end
end)

nope this also doesn’t change anything

Can you record yourself opening the developer console with the following server script:

remotes.CubeCollected.OnServerEvent:Connect(function(player, hit: BasePart, cubeData)
	print("cube collected")
 
	local data = RS.Data:FindFirstChild(player.Name)
	cubeData:Destroy()
		
	data.Stats.Cubes.Value += CubesModule[cubeData.Name].Value
	data["Total Stats"]["Total Cubes"].Value += CubesModule[cubeData.Name].Value
	data.Stats.XP.Value += CubesModule[cubeData.Name].Value/10
end)



local function parseVector3(str)
	local x, y, z = string.match(str, "([^,]+), ([^,]+), ([^,]+)")
	if x and y and z then
		return Vector3.new(tonumber(x), tonumber(y), tonumber(z))
	end
	return nil
end

local function isPositionTaken(pos, takenPositions, tolerance)
	for _, taken in takenPositions do
		if (pos - taken).Magnitude <= tolerance then
			return true
		end
	end
	return false
end

local spawnDebounces = {}

remotes.CubeData.OnServerInvoke = function(player)
	print("function")
	local data = RS.Data:WaitForChild(player.Name)
	local spawnSpeed = data["Cube Stats"].Speed.Value
	local spawnLimit = data["Cube Stats"].Limit.Value
	
	if spawnDebounces[player.Name] or #data.Cubes:GetChildren() >= spawnLimit then return nil end

	spawnDebounces[player.Name] = true

	spawn(function()
		task.wait(1/spawnSpeed - 0.001)
		spawnDebounces[player.Name] = nil
	end)
	
	local chosedCube = CubesModule.ChooseCube()

	local cubeData = Instance.new("StringValue")
	cubeData.Name = chosedCube

	local randomX = math.random(-spawnArea.Size.X/2 + 0.5, spawnArea.Size.X/2 - 0.5)
	local randomZ = math.random(-spawnArea.Size.Z/2 + 0.5, spawnArea.Size.Z/2 - 0.5)
	local pos = spawnArea.Position + Vector3.new(randomX, spawnArea.Size.Y/2 + 0.5, randomZ)

	cubeData.Value = tostring(pos)
	cubeData.Parent = data.Cubes
	
	return cubeData

end

And see if it is spammed?

doesn’t looks like it is being spammed

Can I ask what is the server “spawnLimit” value?

it was 400 i tried setting it to low value but it also doesn’t remove lags

I would reduce that regardless of whether or not it’s calling lag right now since it won’t scale well when you have lots of players.

But with my time limit of 0.2 seconds - you are returning 2,000 entries every second (max - 400 every 0.2 seconds).

How does the CubesModule.ChooseCube() function look?

cubusModule:

local cubes = {
	["Basic"] = {Index = 1, Rarity = 2, Color = Color3.fromRGB(130, 130, 130), Value = 1},
	["Easy"] = {Index = 2, Rarity = 5, Color = Color3.fromRGB(50, 150, 75), Value = 5},
	["Rare"] = {Index = 3, Rarity = 20, Color = Color3.fromRGB(0, 85, 255), Value = 25},
	["Epic"] = {Index = 4, Rarity = 100, Color = Color3.fromRGB(100, 0, 150), Value = 50},
	["Legendary"] = {Index = 5, Rarity = 500, Color = Color3.fromRGB(255, 255, 75), Value = 100},
	["Mythic"] = {Index = 6, Rarity = 2.5e3, Color = Color3.fromRGB(255, 0, 0), Value = 250},
	["Secret_XD"] = {Index = 7, Rarity = 1e12, Color = Color3.fromRGB(0, 0, 0), Value = 1e6},
}

local function CalculateWeights(cubes, luck)
	local weights = {}
	local totalWeight = 0
	for name, v in cubes do
		if type(v) == "table" and v.Rarity then
			local odds = 1 / v.Rarity
			if odds and odds > 0 then
				weights[name] = odds
				totalWeight += odds
			end
		end
	end
	return weights, totalWeight
end

local weights, totalWeight = CalculateWeights(cubes)

function cubes.ChooseCube()
	local rand = (math.random() * totalWeight)
	local cumulative = 0
	for name, weight in weights do
		cumulative += weight
		if rand <= cumulative then
			return name
		end
	end
	return "Basic"
end

return cubes

basically jsut returns random cube name from table

I’m thinking it could be because you’re using a StringValue instance and it’s not actually being garbage collected or cleaned up.

Even though you’re destroying it the client may still keep a reference to the cubeData and it never becomes nil which means the object could remain in memory until released and over-time this memory and reference count is growing so it could lag.

You could try changing your server script to:

remotes.CubeCollected.OnServerEvent:Connect(function(player, hit: BasePart, cubeData)
	print("cube collected")

	local data = RS.Data:FindFirstChild(player.Name)
	if not data or not cubeData then return end

	local cubeConfig = CubesModule[cubeData.Name]
	if not cubeConfig then return end

	data.Stats.Cubes.Value += cubeConfig.Value
	data["Total Stats"]["Total Cubes"].Value += cubeConfig.Value
	data.Stats.XP.Value += cubeConfig.Value / 10
end)

local spawnDebounces = {}

remotes.CubeData.OnServerInvoke = function(player)
	print("function")

	local data = RS.Data:WaitForChild(player.Name)
	local spawnSpeed = data["Cube Stats"].Speed.Value
	local spawnLimit = data["Cube Stats"].Limit.Value

	if spawnDebounces[player.Name] or #data.Cubes:GetChildren() >= spawnLimit then
		return nil
	end

	spawnDebounces[player.Name] = true
	task.delay(1 / spawnSpeed - 0.001, function()
		spawnDebounces[player.Name] = nil
	end)

	local chosedCube = CubesModule.ChooseCube()

	local randomX = math.random(-spawnArea.Size.X/2 + 0.5, spawnArea.Size.X/2 - 0.5)
	local randomZ = math.random(-spawnArea.Size.Z/2 + 0.5, spawnArea.Size.Z/2 - 0.5)
	local pos = spawnArea.Position + Vector3.new(randomX, spawnArea.Size.Y/2 + 0.5, randomZ)

	local cubeData = {
		Name = chosedCube,
		Pos = pos,
	}
	return cubeData
end

And the client script to:

local player = game:GetService("Players").LocalPlayer

local cubeCap = 50
local activeCubes = {}
local cubeHandlerExists = {}
local touchedCubes = {}

local function onTouch(cube, cubeData)
	if cubeHandlerExists[cube] then return end
	if not cube:IsA("BasePart") then return end

	cubeHandlerExists[cube] = true

	local connection = cube.Touched:Connect(function(hit)
		if touchedCubes[cube] then return end
		if not player.Character then return end
		if not hit:IsDescendantOf(player.Character) then return end

		touchedCubes[cube] = true
		table.remove(activeCubes, table.find(activeCubes, cube))

		AnimateCollect(cube)
		Remotes.CubeCollected:FireServer(hit, cubeData) 
		game.Debris:AddItem(cube, 0.6)

		task.delay(0.5, function()
			touchedCubes[cube] = nil
		end)

		if connection then
			connection:Disconnect()
			connection = nil
		end
		cubeHandlerExists[cube] = nil
	end)
end

local function spawnCube()
	if #activeCubes >= cubeCap then return end

	local cubeData = Remotes.CubeData:InvokeServer()
	if not cubeData then return end

	local cubeTemplate = CubesFolder:FindFirstChild(cubeData.Name)
	if not cubeTemplate then return end

	local cube = cubeTemplate:Clone()
	cube.Position = cubeData.Pos 

	onTouch(cube, cubeData)

	local parentFolder = workspace:FindFirstChild(player.Name .. "_Cubes")
	if parentFolder then
		cube.Parent = parentFolder
	end

	table.insert(activeCubes, cube)
end

task.spawn(function()
	while playerCubesFolder do
		if #activeCubes < cubeCap then
			spawnCube()
		end
		task.wait(0.2)
	end
end)

Sorry I realise that the new server script system (not inserting a StringValue into cube data) will break your limit.

Here’s a cleaner version that stores the total count in an object (new server script):

local spawnDebounces = {}
local activeCubeCounts = {}

remotes.CubeCollected.OnServerEvent:Connect(function(player, hit: BasePart, cubeData)
	print("cube collected")

	local data = RS.Data:FindFirstChild(player.Name)
	if not data or not cubeData then return end

	local cubeConfig = CubesModule[cubeData.Name]
	if not cubeConfig then return end

	data.Stats.Cubes.Value += cubeConfig.Value
	data["Total Stats"]["Total Cubes"].Value += cubeConfig.Value
	data.Stats.XP.Value += cubeConfig.Value / 10

	if activeCubeCounts[player] then
		activeCubeCounts[player] -= 1
	end
end)

remotes.CubeData.OnServerInvoke = function(player)
	print("function")

	local data = RS.Data:WaitForChild(player.Name)
	local spawnSpeed = data["Cube Stats"].Speed.Value
	local spawnLimit = data["Cube Stats"].Limit.Value

	activeCubeCounts[player] = activeCubeCounts[player] or 0

	if spawnDebounces[player.Name] or activeCubeCounts[player] >= spawnLimit then
		return nil
	end

	spawnDebounces[player.Name] = true
	task.delay(1 / spawnSpeed - 0.001, function()
		spawnDebounces[player.Name] = nil
	end)

	local chosedCube = CubesModule.ChooseCube()

	local randomX = math.random(-spawnArea.Size.X/2 + 0.5, spawnArea.Size.X/2 - 0.5)
	local randomZ = math.random(-spawnArea.Size.Z/2 + 0.5, spawnArea.Size.Z/2 - 0.5)
	local pos = spawnArea.Position + Vector3.new(randomX, spawnArea.Size.Y/2 + 0.5, randomZ)

	local cubeData = {
		Name = chosedCube,
		Pos = pos,
	}

	activeCubeCounts[player] += 1

	return cubeData
end

You would have to cleanup the activeCubeCounts when a player leaves.

For lag spikes like these, you can also use the MicroProfiler to identify the issue.

i’m doing string value cause i want cubes save after player rejoin creating cubes based on info from stringValue

in CubeCollect event after reward i made

cubeData:Destroy()
cubeData = nil

and in client after firing that remote

cubeData = nil

If they rejoin on a different server it will not save though, correct?

You want it to save if the user rejoins the same server?


converts data to folder in ReplicatedStorage.Data.PlayerName

big apologies for this post problem was not in collecting system but some of my ui functions that is also in client script before all the logic for spawning and collecting was in client only but then i remember that it is too easy for exploit so i moved some logic to server (when everything was in client there was no lags) i really apology for confusing thx to people tried to help (@fast_front)

1 Like