Entities tremble after a period of time

Hey, I created a functionality to move my entities on server side and replicate their positions on client.

After a period of time appear a bug which start to tremble the entities…

I’d like to remove this bug and may be optimize my server-client system,

Variant 1:
(Client)

game:GetService("RunService").Heartbeat:Connect(function()

	for id, entitySaved in pairs(SavedEntities) do
		entitySaved["Model"]:PivotTo(newPath:CalculateUniformCFrame(entitySaved.Progress))
	end
	
end)

(Server)

local totalDistance = EntityMove:calculateTotalDistance(Path)

local serverPart = workspace.ServerSide

RunService.Heartbeat:Connect(function(deltaTime)

	for index, newEntity in ipairs(Entities) do
		local distanceTraveled = newEntity.Speed * deltaTime

		-- Update distance traveled for the entity
		newEntity.DistanceTravelled = (newEntity.DistanceTravelled or 0) + distanceTraveled

		-- Calculate progress for the entity
		local progress = newEntity.DistanceTravelled / totalDistance

		if progress >= 1 then
			ClientReplicator:FireAllClients("Destroy", tostring(newEntity.EntityId))
			table.remove(Entities, index)  -- Remove the entity from the table
		else
			local newCframe = newPath:CalculateUniformCFrame(progress)
			serverPart.CFrame = newCframe
			ClientReplicator:FireAllClients("UpdateProgress", tostring(newEntity.EntityId), progress)
		end
	end
end)

Variant 2:

(Client)

game:GetService("RunService").Heartbeat:Connect(function()

	for id, entitySaved in pairs(SavedEntities) do
		if entitySaved.Cframe then
			entitySaved["Model"]:PivotTo(entitySaved.Cframe)
		end
	end
	
end)

(Server)

local totalDistance = EntityMove:calculateTotalDistance(Path)

local serverPart = workspace.ServerSide

RunService.Heartbeat:Connect(function(deltaTime)

	for index, newEntity in ipairs(Entities) do
		local distanceTraveled = newEntity.Speed * deltaTime

		-- Update distance traveled for the entity
		newEntity.DistanceTravelled = (newEntity.DistanceTravelled or 0) + distanceTraveled

		-- Calculate progress for the entity
		local progress = newEntity.DistanceTravelled / totalDistance

		if progress >= 1 then
			ClientReplicator:FireAllClients("Destroy", tostring(newEntity.EntityId))
			table.remove(Entities, index)  -- Remove the entity from the table
		else
			local newCframe = newPath:CalculateUniformCFrame(progress)
			serverPart.CFrame = newCframe
			ClientReplicator:FireAllClients("UpdatePosition", tostring(newEntity.EntityId), newCframe)
		end
	end
end)

Video example:
From the start

After period of time

I’m fighting with this problem already 3 days. I don’t know how to optimize this to let me at least to move 150 models…

2 Likes

Videos are saying “Video is not ready Ask the video creator to sign up and share this with you again to preview.”

And as for the tremble, can’t really tell what you’re talking about with the tiny preview videos, but I’d assume it’s just due to excessive updates or trying to CFrame too many entities at once on the server.
For such a system, I’d strictly use values and attributes on the server and use those for part based validation/finding things within regions later when they’re necessary, just to ease the load on the server when it comes to the actual movement of the objects.

I hope now you could see the videos…

1 Like

I dont see anything wrong, lol maybe I am crazy.

1 Like
local Functions = {
	["Create"] = function(Entity)

		local entityPacket : EntityPacketData = Entity

		local model = searchforId(tostring(entityPacket.Z)):Clone()
		model.Parent = workspace.Map.Entities
		animationModel:PlayAnim(model, "Run")
		
		task.spawn(function()
			for _, part in ipairs(model:GetDescendants()) do
				if part:IsA("BasePart") then
					part.CollisionGroup = "Entities"
				end
			end
		end)
		
		SavedEntities[tonumber(entityPacket.I)] = {
			["Model"] = model,
			["Speed"] = entityPacket.S,
			["StartTime"] = entityPacket.T,
			["Health"] = entityPacket.H,
			["OrigSpeed"] = entityPacket.OS,
			["Progress"] = entityPacket.P,
			["Cframe"] = entityPacket.C
		}

	end,
	["UpdateHealth"] = function(EntityId, health)
		if SavedEntities[tonumber(EntityId)] then
			SavedEntities[tonumber(EntityId)]["Health"] = health
		end
	end,
	["UpdateStatus"] = function(EntityId, status)
		
	end,
	["UpdateProgress"] = function(EntityId, progress)
		if SavedEntities[tonumber(EntityId)] then
			SavedEntities[tonumber(EntityId)]["Progress"] = progress
		end
	end,
	["UpdatePosition"] = function(EntityId, cframe)
		if SavedEntities[tonumber(EntityId)] then
			SavedEntities[tonumber(EntityId)]["Cframe"] = cframe
		end
	end,
	["UpdateTableProgress"] = function(tableProgress)
		for index, progress in ipairs(tableProgress) do
			SavedEntities[index].Progress = progress
		end
	end,
	["Destroy"] = function(Id : string)

		if SavedEntities[tonumber(Id)] then
			SavedEntities[tonumber(Id)].Model:Destroy()
			SavedEntities[tonumber(Id)] = nil

		end

	end,
}

game:GetService("RunService").Heartbeat:Connect(function()

	for id, entitySaved in pairs(SavedEntities) do
		entitySaved["Model"]:PivotTo(newPath:CalculateUniformCFrame(entitySaved.Progress))
	end
	
end)

ClientReplicator.OnClientEvent:Connect(function(func,...)

	if Functions[func] then

		Functions[func](...)

	end

end)

Here’s the client part almost full code.

And here’s the server side:

playerAddEvent = game:GetService("Players").PlayerAdded:Connect(function(player: Player)
	characterAddEvent = player.CharacterAdded:Connect(function()
		if player.Character then
			if playerAddEvent then
				playerAddEvent:Disconnect()
			end
			if characterAddEvent then
				characterAddEvent:Disconnect()
			end
			task.spawn(function()
					print(EntityData)
					for Entity = 1, WaveSegment.Count, 1 do

						currentEntityId += 1

						local offset = tostring(math.random(-100,100)/100)

						Entities[#Entities + 1] = {
							["EntityId"] = currentEntityId,
							["Speed"] = EntityData.Speed,
							["OrigSpeed"] = EntityData.OrigSpeed,
							["Health"] = EntityData.MaxHealth,
							["Progress"] = 0,
							["Status"] = EntityData.Status,
							["DistanceTravelled"] = 0
						}

						ClientReplicator:FireAllClients("Create",{
							["Z"] = tostring(WaveSegment.Entity),
							["I"] = tostring(currentEntityId),
							["S"] = tostring(EntityData.Speed),
							["OS"] = tostring(EntityData.OrigSpeed),
							["H"] = tostring(EntityData.MaxHealth),
							["P"] = EntityData.Progress,
							["C"] = EntityData.C
						})

						task.wait(WaveSegment.DelayTime)

					end

			end)
		end
		
				
	end)
end)

local totalDistance = EntityMove:calculateTotalDistance(Path)

local serverPart = workspace.ServerSide

RunService.Heartbeat:Connect(function(deltaTime)
	
	local entityTable = {}
	for index, newEntity in ipairs(Entities) do
		local distanceTraveled = newEntity.Speed * deltaTime

		-- Update distance traveled for the entity
		newEntity.DistanceTravelled = (newEntity.DistanceTravelled or 0) + distanceTraveled

		-- Calculate progress for the entity
		local progress = newEntity.DistanceTravelled / totalDistance
		
		newEntity.Progress = progress

		if progress >= 1 then
			ClientReplicator:FireAllClients("Destroy", tostring(newEntity.EntityId))
			table.remove(Entities, index)  -- Remove the entity from the table
		else
			local newCframe = newPath:CalculateUniformCFrame(progress)
			serverPart.CFrame = newCframe
			--ClientReplicator:FireAllClients("UpdateProgress", tostring(newEntity.EntityId), progress)
			table.insert(entityTable, newEntity.Progress)
		end
	end
	
	ClientReplicator:FireAllClients("UpdateTableProgress", entityTable)
end)

New server code to try:

playerAddEvent = game:GetService("Players").PlayerAdded:Connect(function(player: Player)
	characterAddEvent = player.CharacterAdded:Connect(function()
		if player.Character then
			if playerAddEvent then
				playerAddEvent:Disconnect()
			end
			if characterAddEvent then
				characterAddEvent:Disconnect()
			end
			task.spawn(function()
				print(EntityData)
				for Entity = 1, WaveSegment.Count, 1 do

					currentEntityId += 1

					local offset = tostring(math.random(-100,100)/100)

					Entities[#Entities + 1] = {
						["EntityId"] = currentEntityId,
						["Speed"] = EntityData.Speed,
						["OrigSpeed"] = EntityData.OrigSpeed,
						["Health"] = EntityData.MaxHealth,
						["Progress"] = 0,
						["Status"] = EntityData.Status,
						["DistanceTravelled"] = 0
					}

					ClientReplicator:FireAllClients("Create",{
						["Z"] = tostring(WaveSegment.Entity),
						["I"] = tostring(currentEntityId),
						["S"] = tostring(EntityData.Speed),
						["OS"] = tostring(EntityData.OrigSpeed),
						["H"] = tostring(EntityData.MaxHealth),
						["P"] = EntityData.Progress,
						["C"] = EntityData.C
					})

					task.wait(WaveSegment.DelayTime)

				end

			end)
		end


	end)
end)

local totalDistance = EntityMove:calculateTotalDistance(Path)

local serverPart = workspace.ServerSide

RunService.Heartbeat:Connect(function(deltaTime)

	local entityTable = {}
	for index, newEntity in ipairs(Entities) do
		local distanceTraveled = newEntity.Speed * deltaTime

		-- Update distance traveled for the entity
		newEntity.DistanceTravelled = (newEntity.DistanceTravelled or 0) + distanceTraveled

		-- Calculate progress for the entity
		local progress = newEntity.DistanceTravelled / totalDistance

		newEntity.Progress = progress

		if progress >= 1 then
			ClientReplicator:FireAllClients("Destroy", tostring(newEntity.EntityId))
			table.remove(Entities, index)  -- Remove the entity from the table
		else
			--local newCframe = newPath:CalculateUniformCFrame(progress)
			--> I've just realized this is a redundant calculation since you generate this in Heartbeat for the entities on the Client.
			--serverPart.CFrame = newCframe
			--> See if removing these two improves performance at all
			--ClientReplicator:FireAllClients("UpdateProgress", tostring(newEntity.EntityId), progress)
			--> Thank god you didn't go through with this spam and just bulk updated!
			entityTable[index] = progress
			--> Insert seems unnecessary, as we already have the index for this specific entity (and this prevents possible edge cases with wrong indices)
			--> Also eliminated an unnecessary table index (newEntity.Progress is the same as the local variable "progress")
		end
	end

	ClientReplicator:FireAllClients("UpdateTableProgress", entityTable)
end)

New client code to accompany said server code: (very minor changes)

local Functions = {
	["Create"] = function(Entity)

		local entityPacket : EntityPacketData = Entity

		local model = searchforId(tostring(entityPacket.Z)):Clone()
		model.Parent = workspace.Map.Entities
		animationModel:PlayAnim(model, "Run")

		task.spawn(function()
			for _, part in ipairs(model:GetDescendants()) do
				if part:IsA("BasePart") then
					part.CollisionGroup = "Entities"
				end
			end
		end)

		SavedEntitites[tonumber(entityPacket.I)] = {
			["Model"] = model,
			["Speed"] = entityPacket.S,
			["StartTime"] = entityPacket.T,
			["Health"] = entityPacket.H,
			["OrigSpeed"] = entityPacket.OS,
			["Progress"] = entityPacket.P,
			["Cframe"] = entityPacket.C
		}

	end,
	["UpdateHealth"] = function(EntityId, health)
		if SavedEntities[tonumber(EntityId)] then
			SavedEntities[tonumber(EntityId)]["Health"] = health
		end
	end,
	["UpdateStatus"] = function(EntityId, status)

	end,
	["UpdateProgress"] = function(EntityId, progress)
		if SavedEntities[tonumber(EntityId)] then
			SavedEntities[tonumber(EntityId)]["Progress"] = progress
		end
	end,
	["UpdatePosition"] = function(EntityId, cframe)
		if SavedEntities[tonumber(EntityId)] then
			SavedEntities[tonumber(EntityId)]["Cframe"] = cframe
		end
	end,
	["UpdateTableProgress"] = function(tableProgress)
		for entityID, progress in pairs(tableProgress) do --> ipairs swapped for pairs as it may be interrupted by nonexistant indices as code runs.
			SavedEntities[entityID].Progress = progress
		end
	end,
	["Destroy"] = function(Id : number)

		if SavedEntities[Id] then
			
			SavedEntities[Id].Model:Destroy()
			SavedEntities[Id] = nil

		end

	end,
}

game:GetService("RunService").Heartbeat:Connect(function()

	for id, entitySaved in pairs(SavedEntities) do
		entitySaved["Model"]:PivotTo(newPath:CalculateUniformCFrame(entitySaved.Progress))
	end

end)

ClientReplicator.OnClientEvent:Connect(function(func,...)

	if Functions[func] then

		Functions[func](...)

	end

end)

Let me know how all of this functions, and if it assists the issue.

Still shaking

After some investigation I find out that this is the server side problem, and I don’t know what’s wrong, because I started to move parts on server and near the end they’re also start shaking

Sorry! I had some stuff to do these past two days.

I’m willing to bet that it’s simply too many entities to handle with one script synchronously.

I’d try splitting the entities handling to different threads or even just different scripts. It’s completely possible that there’s just too much going on with all of them being handled by one loop.

But this happen even if there are only 5 entities. Just the first one is almost right

I even removed the calculation of CFrame and just send the progress of entities with one entire package to client. And on client the calculations were done. But still this bug. I tried to check the overloading of server and client. And it’s almost time 0% on both sides (50 entities) may be 1% but rare. Also I changed remote events to unreliable remote events in hope that there is too frequent update, but not…

The performance stats with 50 entities is “sent” = 1.5-2kb/s and “recv” = 30kb/s

I’m not sure if it could be an error with module bezier curve…
Because the old calculation like newPath:CalculateUniformCFrame((workspace:GetServerTimeNow() - tonumber(newEntity.StartTime)) / (Length / tonumber(newEntity.Speed)))

Is working good, but it’s hard to implement something like stun, slow, knockback

Having this in mind, I’d have to assume you just need to split the path up.

Could you send me some of the code for the generation of the path? (As well as any support functions, like EntityMove:calculateTotalDistance)

If you feel more comfortable DMing me the code, my Discord is CantBeBothered