Most performant way to always keep track of all player positions on the server?

I am trying to find the best way to constantly and efficiently keep track of a players last position in a table. I need to keep track of players positions constantly in a heartbeat so I can do player rollback for verifying hits on the server. My current code works fine except for the fact that the performance with just one player sometimes can go up to 1% in activity. I know there is a way to make this more performant. I looked to parallel Lua but most of the documentation is hard to understand and I’m having trouble trying to figure out a good way to apply it to my current situation. If anyone has done something like this in the past or has a good idea to making this more efficient, your help would be greatly appreciated.

The code(I know its not organized but this is all in a testing place so when I move it into my main game I will actually organize things):

--Services
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Debris = game:GetService("Debris")

--Varibles
local savedPos = {}
local players = {}

--Functions
game.Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local rootPart = character:WaitForChild("HumanoidRootPart")
		players[player] = rootPart
	end)
end)

game.Players.PlayerRemoving:Connect(function(player)
	players[player] = nil
end)

RunService.Heartbeat:Connect(function()
	for index, savedTime in pairs(savedPos) do
		if savedTime.ServerTime < game.Workspace:GetServerTimeNow() - 1 then
			table.remove(savedPos, index)
		end
	end
	savedPos[#savedPos + 1] = {["ServerTime"] = game.Workspace:GetServerTimeNow(), ["Players"] = {}}
	
	for i, v in pairs(game.Players:GetPlayers()) do
		if players[v] then
			savedPos[#savedPos].Players[v] = {["Position"] = players[v].Position}
		end
	end
end)
3 Likes

Im def not an expert, but, maybe you could save a player’s position if they’ve moved more than a certain amount of studs (instead of constantly checking cause checking constantly when they’re not moving is not good performance wise). This also might not be what you’re looking for but yeah.

THIS IS PSEUDOCODE:

if a player moved more than x studs then
-- then save their position
end
1 Like

I need to save a players positions constantly to the current server time, when players stand still the performance is fine, its when they start moving and jumping around when the performance of the script goes higher than it should be. I also remove times from the table if their time is more than one second old and that helps save memory and performance.

2 Likes

Do you know what is exactly causing performance to spike up exponentially? And uhhh you could use MemoryStoreService cause you mentioned getting rid of indexes after some time (just a suggestion).

I’m prolly gonna be doing something so sorry if I don’t respond.

2 Likes

I don’t see how memory store service would help as that is more used for communicating information between servers fast. I am doing this on a singular server. What I think is causing the most performance is constantly looping through the current players, and the main table when I remove old indexes. I looked to parallel Lua for this as you can run code as different threads and a system like this would defiantly benefit with a good implementation using it but I just don’t understand the best way to use it for this system.

1 Like

From my knowledge Im guessing its due to this table.remove being unoptimal as it has to loop through the entire table and decrease the index by one.

I recommend using a circular buffer datastructure as loleris recommends:

1 Like

Thanks for the suggestion, what would be the best way to implement this exactly? I looked through the post you sent and the wiki article about circular buffers and I’m a bit stomped.

Edit: So I came up with some code that I think is kind of like a circular buffer. I’m still not sure if I’m correctly overwriting the table every second but it has helped performance a bit. It now only spikes every so often to .300% or .400%.

Here’s the code:

local maxIndex = 100
local nextIndex = 1

RunService.Heartbeat:Connect(function(dt)
	savedPos[nextIndex] = {["ServerTime"] = workspace:GetServerTimeNow(), ["Players"] = {}}
	
	for i, v in pairs(game.Players:GetPlayers()) do
		if players[v] then
			savedPos[nextIndex].Players[v] = {["Position"] = players[v].Position}
		end
	end
	
	nextIndex += 1
	if nextIndex > maxIndex then
		nextIndex = 1
	end
end)
1 Like

The majority of the execution time of your Heartbeat handler is spent calling workspace:GetServerTimeNow(). Instead of calling it for every comparison, call it once at the beginning and store it in a variable.

You can similarly localize the table that holds player positions and players’ root parts to reduce the number of table indexes you need to make, although the performance gain of this isn’t that much.

Try this:

RunService.Heartbeat:Connect(function()
	local serverTime = game.Workspace:GetServerTimeNow()
	local removeTime = serverTime - 1

	do
		local clearIndex
		for index, savedTime in ipairs(savedPos) do
			if savedTime.ServerTime >= removeTime then
				clearIndex = index - 1	--Remove all elements up to and including last one.
				break
			end
		end
		
		if clearIndex then
			--Shift everything down.
			local totalTimes = #savedPos
			table.move(savedPos, clearIndex + 1, totalTimes, 1)
			
			--Clean up end of array.
			for index = totalTimes - clearIndex + 1, totalTimes do
				savedPos[index] = nil
			end
		end
	end

	local savedPlayers = {}

	savedPos[#savedPos + 1] = {
		["ServerTime"] = serverTime,
		["Players"] = savedPlayers
	}

	for i, v in pairs(game.Players:GetPlayers()) do
		local rootPart = players[v]
		if rootPart then
			savedPlayers[v] = {["Position"] = rootPart.Position}
		end
	end
end)

Thanks for the reply, this does seem to help the performance a tiny bit. I know there is a way to do something like this and have very tiny script activity. In this post: Should I run my anticheat script in parallel? If so how? - #5 by BullfrogBait BullfrogBait gets very little script activity using parallel lua. But as I stated originally the resources for it are not clear and even in his post he does not go more into depth of how he did it.

Running things in parallel won’t inherently reduce the amount of work being done, only when it gets done. In the post you linked, the 0.5% script activity of each actor in the 8-player parallel test still adds up to the 4% script activity of the non-parallel test; what makes them more efficient is that they are calculated at literally the same time, so the amount of real time it takes to run is lower than the total amount of execution time. Beyond that, there is an overhead with getting information in and out of Actors, which you would need to do a lot of to read the information gathered, whereas BullfrogBait’s anti-cheat probably only needs to communicate with other systems when something is amiss.

On my machine, the execution time of your original Heartbeat handler was about 0.000045 seconds (45 microseconds) when it was just me in the server. The revisions I posted got it down to around 15 microseconds. For reference, one frame at 60 FPS is 16,666 microseconds. I’m not entirely sure what script activity measures, but from a raw execution time standpoint, you’re not really spending that much even with your original setup.

2 Likes

Thank you, this helped me understand a lot. I’ll just mark your answer as the solution as it seems there isn’t much more I can do to improve this. Thanks again!!

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.