Cloning player avatar to a worldmodel on the server every frame

In my efforts to reduce network latency whilst also getting server sided hit registration to work, someone had suggested to save the player avatar each frame for up to a second. So that in the future, the server can get which worldmodel was closest to the point in time when the player swung their sword, and raycast onto that instead.
The problem is, the way I had done it is extremely performance intensive, and not at all like how CloneTrooper had did it in his tweet.

-- Game services
local RunService = game:GetService("RunService")
local ServerStorage = game:GetService("ServerStorage")


local function AddModels()
	for _, Characters in pairs(workspace:GetChildren()) do
		if Characters:FindFirstChildWhichIsA("Humanoid") then
			local WorldModel = Instance.new("WorldModel", workspace.Folder)
			WorldModel.Name = Characters.Name.."_"..tostring(tick())
			
			Characters.Archivable = true
			local CharModel = Characters:Clone()
			CharModel.Parent = WorldModel
			CharModel.PrimaryPart.Anchored = true
			
			for _, part in CharModel:GetDescendants() do
				if part:IsA("BasePart") and part.Parent == CharModel then
					part.CanCollide = false
					part.Anchored = true
				else
					part:Destroy()
				end
			end
		end
	end
end

local function RemoveOldModels()
	for _, WorldModel in pairs(workspace.Folder:GetChildren()) do
		local split = WorldModel.Name:split("_")
		local PrevTick = tonumber(split[2])
		
		if tick() - PrevTick > 1 then
			WorldModel:Destroy()
		end
	end
end

local timeConsumed = 0
RunService.Heartbeat:Connect(function(dt)
	AddModels()

	timeConsumed += dt
	if (timeConsumed >= 1) then	
		RemoveOldModels()
		timeConsumed = 0
	end
end)


If you want to recreate my problem, put this into a script in ServerScriptService and create a folder in workspace.

1 Like

Instead of creating a new clone have you tried a a caching method, like part cache.

I’ve never used this module before so I have some questions.

  1. Since I need to save the state of the player avatar every frame for up to a second, would I have to created a bunch of precreated parts? How many precreated parts would I need?

  2. And what if a new player joins, would I have to create a part cache for each individual player?

  1. You can just play test it. One crude solution I use is to just set it to 1 precreated part and increase it whenever you get the warning. Otherwise you should be able to calculate it IDK the parameters and inputs into your current system my brain isn’t working rn.
  1. Yes

’
I’m not sure if this is the cause, but you shouldn’t be creating a new world model per character per frame, but only one a frame for all characters;

-- Replacement for AddModels (I think this name is better)
local function CreateNewSnapshot()
	local Snapshot = Instance.new("WorldModel")
	Snapshot.Name = workspace.DistributedGameTime
	Snapshot.Parent = ServerStorage.Snapshots
	
	for _, Character in workspace:GetChildren() do
		if not Character:FindFirstChildWhichIsA("Humanoid") then
			continue
		end
		
		Character.Archivable = true;
		-- Because it's a world model and we are cloning everything,
		-- all of the joints SHOULD be rotated correctly.
		-- And also, there's not really a point in removing extra objects since
		-- WorldModels will not even attempt to interpret them.
		Character:Clone().Parent = Snapshot
	end
end

-- Replacement for RemoveModels (I think this name is better as well)
local function RemoveOldSnapshots()
	for _, Snapshot in ServerStorage.Snapshots:GetChildren() do
		local SnapshotTime = tonumber(Snapshot.Name)
		
		if workspace.DistributedGameTime - SnapshotTime > 1 then
			Snapshot:Destroy()
		end
	end
end

RunService.Heartbeat:Connect(function(dt)
	CreateNewSnapshot()
	RemoveOldSnapshots()
end)

Yeah I just don’t know what is making it so laggy, even using your suggestion, and when it’s only one player character in workspace, it runs quite poorly.
One other thing to note, the player animation doesn’t replicate to the worldmodel, unlike in CloneTrooper’s example.

So I tried your suggestion, and I have a feeling I’m using the module wrong.
Nothing in this code actually works, mostly because, whenever the player spawns in, it spawns them in the same place where the partcache are stored for whatever reason.

-- Modules
local PartCacheModule = require(script:WaitForChild("PartCache"))
local PlayerCache = {}

-- Replacement for AddModels (I think this name is better)
local function CreateNewSnapshot()
	local Snapshot = Instance.new("WorldModel")
	Snapshot.Name = workspace.DistributedGameTime
	Snapshot.Parent = workspace.Snapshots

	for _, Character in workspace:GetChildren() do
		if not Character:FindFirstChildWhichIsA("Humanoid") then
			continue
		end

		local Model = Instance.new("Model", Snapshot)
		Model.Name = Character.Name

		for _, part in pairs(Character:GetChildren()) do
			if part:IsA("BasePart") then
				local Part = PlayerCache[Character.Name][part.Name]:GetPart()
				Part.Parent = Model
				Part.CFrame = part.CFrame
				Part.Anchored = true
				Part.CanCollide = false
			end
		end
	end
end

-- Replacement for RemoveModels (I think this name is better as well)
local function RemoveOldSnapshots()
	for _, Snapshot in workspace.Snapshots:GetChildren() do
		local SnapshotTime = tonumber(Snapshot.Name)

		if workspace.DistributedGameTime - SnapshotTime > 1 then
			for _, Character in pairs(Snapshot:GetChildren()) do
				for _, Part in pairs(Character:GetChildren()) do
					PlayerCache[Character.Name][Part.Name]:ReturnPart()
				end
			end
			Snapshot:Destroy()
		end
	end
end


RunService.Heartbeat:Connect(function(dt)
	CreateNewSnapshot()
	RemoveOldSnapshots()
end)


-- Also have a start function which saves NPC character to a table too
local function CreateCache(player)
	local Table = {}
	player.CharacterAdded:Wait()
	for _, part in pairs(player.Character:GetChildren()) do
		if part:IsA("BasePart") then
			local Part = part:Clone()
			Table[part.Name] = PartCacheModule.new(Part, 1)
		end
	end

	PlayerCache[player.Name] = Table

	print(PlayerCache)
end

local function onPlayerAdded(player)
	CreateCache(player)
end

Players.PlayerAdded:Connect(onPlayerAdded)

Clone the model not the part.

The part will have motor6ds that are still attached to the original body when cloned.

Are you sure this module accepts models? Because I run into this error when I changed the code.

local function CreateCache(player)
	player.CharacterAdded:Wait()
	
	PlayerCache[player.Name] = PartCacheModule.new(player.Character, 1)
	print(PlayerCache)
end

local function onPlayerAdded(player)
	CreateCache(player)
end

Players.PlayerAdded:Connect(onPlayerAdded)

image

Old post but there’s no reason to copy the entire character model. Think of it as just adding in a brand new dummy but with the player’s position and pose in that frame.