Feedback on rewind script

Hi guys! I have created the following code for hitreg systems in my game!

local entities = {}
local savedRewinds = {}
local index = 1

local Current_Step = 0
local Max_Savelength = 60


--function that inserts an entity into the array
local function AddNewEntity(e)
	local data = {}
	for _, i in pairs(e:GetChildren()) do
		if i:IsA("BasePart") then
			data[#data+1] = i
		end
	end
	data.Hum = e:FindFirstChildOfClass("Humanoid")
	
	entities[index] = data
	index = index + 1
end

--function to locate data based on a given humanoid
local function getDataFromHumanoid(hum)
	local humSet
	local ind
	for x, i in pairs(entities) do
		if i.Hum == hum then
			humSet = i
			ind = x
			break
		end
	end
	
	if humSet then
		return humSet, ind 
	else
		return false
	end
end

--function to retrieve data at given time
local function getEntityCopyAtTime(humanoid, timestamp)
	--retrieve entity parts
	local humSet, ind = getDataFromHumanoid(humanoid)
	if not humSet then print("humanoid not found in data!") return false end
	
	
	--retrieve chronologically closest data
	local nearestData
	local nearestDist = math.huge
	for _, i in pairs(savedRewinds) do
		
		local saveTime = i.timestamp
		if not nearestData then nearestData = i continue end
		local timeDist = math.abs(timestamp-saveTime)
		if timeDist < nearestDist then
			nearestData = i
			nearestDist = timeDist
		end
	end
	
	--get data specific to this humanoid
	local humData
	for x, i in pairs(nearestData) do
		if x == "timestamp" then continue end
		local saveHum = i.Hum
		if saveHum == humanoid then
			humData = i
		end
	end
	if not humData then print("humanoid not found at timestamp!") return false end
	
	--build copy and position it based on saved data
	local entityCopy = {}
	for x, i in pairs(humSet) do
		if i:IsA("BasePart") then
			local partCopy = i:Clone()
			partCopy:ClearAllChildren()
			entityCopy[x] = partCopy
		end
	end
	for x, i in pairs(entityCopy) do
		if x == "Hum" then continue end
		i.Anchored = true
		i.CFrame = humData[x]
	end
	return entityCopy
end



--insert all objects that already exists
for _, i in pairs(game.Workspace.Entities:GetChildren()) do
	AddNewEntity(i)
end

--connect the childadded event to the addnewentity function
game.Workspace.Entities.ChildAdded:Connect(AddNewEntity)


--update function 
game:GetService("RunService").Heartbeat:Connect(function()
	--new entry
	local data = {}
	data.timestamp = tick()
	
	--increment step
	Current_Step = (Current_Step+1)%Max_Savelength
	
	--build entry
	for x, entity in pairs(entities) do
		local entityEntry = {}
		for v = 1, #entity do --   -1
			local part = entity[v]
			if part then
				entityEntry[v] = part.CFrame
			end
		end
		entityEntry.Hum = entity.Hum
		data[x] = entityEntry
	end
	
	--insert entry
	savedRewinds[Current_Step] = data
	
	--update server time
	game.ReplicatedStorage.ServerTime.Value = tick()
end)

The program registers each entity in the game, saving in an array all of that entity’s pieces and the entity’s humanoid. Then using the same indices in which the pieces were registered, it saves the CFrame of each piece in an array. Later on, the function getEntityCopyAtTime() can accept a humanoid and a timestamp as parameters, and will look for the dataset containing that humanoid that is chronologically closest to the time provided. the code is configured to save CFrames up to 60 instances, at which point the code will start overwriting the oldest instances in the array. assuming the average heartbeat is .05 seconds, this configuration will allow the game to look up to 3 seconds into the past to see where players were, allowing a maximum effective ping of 3000 for accurate hit registration.

In the gif below, I tested the code by having it place a copy of my character in the world from 2 seconds prior every time I press the L key.
651135600922e389916ef5fa7706c800

current pros and cons of this code:

pros:

  • allows for up to 3 seconds of rewind vision for the server
  • accurate up to approximately .05 seconds of error margin

cons:

  • data heavy. ~16 players per server * 16 pieces per player character * 60 time instances saved = roughly 15,360 CFrame values saved to memory at any given moment, or 983,040 bytes, which is equal to almost a megabyte. (okay, maybe not actually that bad)
  • lossy. Every time this data needs to be referenced for hit registration, the code duplicates the character model and positions it on the indicated CFrames. this duplicated model is not cached, as the many differences in character piece colliders and piece sizes would make standardized caching difficult.

if you guys have any recommendations to improve upon this, I’d love to hear them!

1 Like