I am making a system to pick up or harvest items to add them into an inventory. I want the system to run without lag, which I was not expecting to have to deal with. It seems to be caused by calling :Destroy().
The first time I pick up an item in a playtest, the server lags fairly consistently on the 3-4th and 34-35th frame after calling :Destroy() on the script’s parent and the script itself.
Here’s a video:
The script output runs every frame and is the time passed between each RunService.Hearbeat:Wait(). This output is from another playtest:
Output
15:12:19.313 Destroyed - Studio
15:12:19.313 1 : 0.0004653000505641103 - Studio
15:12:19.313 nil - Studio
15:12:19.329 2 : 0.015778600005432963 - Studio
15:12:19.329 nil - Studio
15:12:19.346 3 : 0.016781099955551326 - Studio
15:12:19.346 nil - Studio
15:12:19.643 4 : 0.2972885000053793 - Studio
15:12:19.643 nil - Studio
15:12:19.655 5 : 0.011784399976022542 - Studio
15:12:19.655 nil - Studio
15:12:19.663 6 : 0.008206300088204443 - Studio
15:12:19.664 nil - Studio
15:12:19.678 7 : 0.015434199944138527 - Studio
15:12:19.679 nil - Studio
15:12:19.695 8 : 0.016785999992862344 - Studio
15:12:19.695 nil - Studio
15:12:19.712 9 : 0.0171703000087291 - Studio
15:12:19.713 nil - Studio
15:12:19.728 10 : 0.015445300028659403 - Studio
15:12:19.728 nil - Studio
15:12:19.747 11 : 0.018666299991309643 - Studio
15:12:19.747 nil - Studio
15:12:19.762 12 : 0.015251799952238798 - Studio
15:12:19.762 nil - Studio
15:12:19.779 13 : 0.01716130005661398 - Studio
15:12:19.779 nil - Studio
15:12:19.795 14 : 0.01576999993994832 - Studio
15:12:19.795 nil - Studio
15:12:19.812 15 : 0.017185600008815527 - Studio
15:12:19.812 nil - Studio
15:12:19.828 16 : 0.015714699984528124 - Studio
15:12:19.828 nil - Studio
15:12:19.845 17 : 0.017381300101988018 - Studio
15:12:19.845 nil - Studio
15:12:19.862 18 : 0.016752799972891808 - Studio
15:12:19.862 nil - Studio
15:12:20.159 19 : 0.29618359997402877 - Studio
15:12:20.159 nil - Studio
15:12:20.166 20 : 0.007649100036360323 - Studio
15:12:20.166 nil - Studio
15:12:20.180 21 : 0.014091299963183701 - Studio
15:12:20.180 nil - Studio
15:12:20.195 22 : 0.015474600018933415 - Studio
15:12:20.195 nil - Studio
15:12:20.212 23 : 0.016644999966956675 - Studio
15:12:20.212 nil - Studio
15:12:20.229 24 : 0.017265900038182735 - Studio
15:12:20.229 nil - Studio
15:12:20.246 25 : 0.016572199994698167 - Studio
15:12:20.246 nil - Studio
15:12:20.263 26 : 0.0177854000357911 - Studio
15:12:20.264 nil - Studio
15:12:20.279 27 : 0.015309599926695228 - Studio
15:12:20.279 nil - Studio
15:12:20.301 28 : 0.021857100073248148 - Studio
15:12:20.301 nil - Studio
15:12:20.316 29 : 0.015378799987956882 - Studio
15:12:20.316 nil - Studio
15:12:20.329 30 : 0.013008299982175231 - Studio
15:12:20.329 nil - Studio
15:12:20.349 31 : 0.019523499999195337 - Studio
15:12:20.349 nil - Studio
15:12:20.362 32 : 0.013953399960882962 - Studio
15:12:20.363 nil - Studio
15:12:20.379 33 : 0.016509099979884923 - Studio
15:12:20.379 nil - Studio
15:12:20.396 34 : 0.017109000007621944 - Studio
15:12:20.397 nil - Studio
15:12:20.414 35 : 0.01808429998345673 - Studio
15:12:20.415 nil - Studio
15:12:20.428 36 : 0.014261500095017254 - Studio
15:12:20.429 nil - Studio
15:12:20.445 37 : 0.016366799944080412 - Studio
15:12:20.445 nil - Studio
The time passed for frame 4 and 19 is higher than average, which is when the game lags. It’s a server side issue, not a client side one. When playtesting with the roblox player, framerate doesn’t drop, but if you’re walking during those frames you teleport back to where you were (since the server is lagging, causing a client-server difference).
After commenting out different parts of code, I have discovered that calling :Destroy() is lagging my game. When I comment that line of code out, the lag goes away. However, this is undesirable since, well, when you pick up an item you don’t want it to stay around! I tried finding workarounds, but using :Remove() or game.debris causes the same lag. I could theoretically set the part’s CanCollide to false and Transparency to 1, but that feels like a cheap fix and would probably lead to a lot of trash in the workspace.
My code is currently a script calling a ModuleScript. I’ve put the script and the relevant part of the ModuleScript here. The ModuleScript calls a few other “item” functions inside itself, but those are not causing the lag since when they’re commented out, the lag persists. I can post them if they would be helpful.
ModuleScript
-- the lag causing part is at the bottom of this function
function module.registerHarvestable(callingScript : Script, harvestable : Part, harvestableType : string, harvestTime : number, itemToGive : item, itemCount : number, durability : number, onDestroy)
if not durability then
durability = 1
end
if not onDestroy then
onDestroy = function() return nil end
end
-- make the proximityPrompt
local harvestPrompt = harvestDefaultProximityPrompt:Clone()
harvestPrompt.ObjectText = itemToGive.itemName
local actionAnimation
if harvestableType == module.itemEnum.harvestableTypes.axeNecessary then
harvestPrompt.ActionText = "Chop"
actionAnimation = script.MiningAnimation
elseif harvestableType == module.itemEnum.harvestableTypes.pickaxeNecessary then
harvestPrompt.ActionText = "Mine"
actionAnimation = script.MiningAnimation
elseif harvestableType == module.itemEnum.harvestableTypes.shovelNecessary then
harvestPrompt.ActionText = "Dig"
warn("There is no dig animation yet!")
elseif harvestableType == module.itemEnum.harvestableTypes.instant then
harvestPrompt.ActionText = "Pick up"
actionAnimation = script.PickupAnimation
harvestTime = 0
end
harvestPrompt.Parent = harvestable
local destroyHarvestable = false
-- when a player clicks we begin the collecting animation, and if the player doesn't cancel it they harvest the item!
local harvestConnection = harvestPrompt.Triggered:Connect(function(playerWhoTriggered: Player)
local harvestSuccess = false
-- get the player to move to the item to harvest
local character = playerWhoTriggered.Character
local humanoid : Humanoid = character:FindFirstChild("Humanoid")
local hrp : Part = character:FindFirstChild("HumanoidRootPart")
local animator : Animator = humanoid:FindFirstChild("Animator")
local actionTrack = animator:LoadAnimation(actionAnimation)
if harvestTime > 0 then
startHarvestingAnimEvent:FireClient(playerWhoTriggered)
end
-- first we make the player actually look at the harvestable part
local partToPlayer = Vector3.new(hrp.Position.X - harvestable.Position.X, 0, hrp.Position.Z - harvestable.Position.Z)
partToPlayer = (partToPlayer / partToPlayer.Magnitude) * 3 -- 3 is the amount of studs away that we stand from the object.
-- it may need to be customizable. just FYI
-- we don't do this if we're picking it up instantly
if harvestTime > 0 then
-- move to. When finished, run the startHarvesting event so we start mining
local finalVector = Vector3.new(partToPlayer.X + harvestable.Position.X, hrp.Position.Y, partToPlayer.Z + harvestable.Position.Z)
humanoid:MoveTo(finalVector)
wait()
local timePassed = 0
-- for some reason moveFinished was unreliable. IDK why. So I just made my own move finished
repeat
timePassed += wait()
until (hrp.Position - finalVector).Magnitude < 2.5 or timePassed > 5
end
hrp.CFrame = CFrame.new(hrp.Position, Vector3.new(harvestable.Position.X, hrp.Position.Y, harvestable.Position.Z))
wait()
-- start animating
actionTrack:Play()
local forceHarvestStop = false
local playerStopConnection = stopHarvestingEvent.OnServerEvent:Connect(function(player: Player, ...: any)
if player == playerWhoTriggered then
forceHarvestStop = true
harvestSuccess = false
end
end)
local timePassed = 0
harvestSuccess = true
while (not forceHarvestStop) and timePassed < harvestTime do
timePassed += wait()
end
wait()
-- again, we don't need to tell the client to stop or stop the animation if we're picking up the item instantly
if harvestTime > 0 then
stopHarvestingEvent:FireClient(playerWhoTriggered)
actionTrack:Stop()
end
-- if success, great! we give the items
if harvestSuccess then
local inventory = playerWhoTriggered:FindFirstChild("Inventory")
if inventory then
for i = 1, itemCount, 1 do
--local newItem : item = module.newItem(itemToGive)
--local newValue = module.itemTypeToValue(newItem)
--newValue.Parent = inventory
end
end
durability -= 1
if durability == 0 then
harvestPrompt.Enabled = false
task.spawn(function ()
onDestroy()
destroyHarvestable = true
end)
end
end
end)
task.spawn(function()
repeat
wait()
until destroyHarvestable
print("Get Ready!")
task.wait(5)
harvestConnection:Disconnect()
harvestable:Destroy() -- it's this line. Commenting this out resolves the lag.
callingScript:Destroy() -- this line too. But if I comment this, the picked up part stays!
local startTime = tick()
game:GetService("RunService").Heartbeat:Wait()
print(tick() - startTime)
end)
end
I tried using task.spawn() so the destruction could be handled asynchronously, but it didn’t fix anything. That’s why the convoluted system for the part’s destruction exists (destroyHarvestable is set to true, which causes the thread’s loop to stop).
Script (really just a call to the ModuleScript)
local itemScript = require(game:GetService("ServerScriptService").ModuleScripts.ItemScript)
local rs = game:GetService("RunService")
itemScript.registerHarvestable(
script,
script.Parent,
itemScript.itemEnum.harvestableTypes.instant,
0,
itemScript.getItemByName("Normal Stone"),
2,
1,
-- this is a callback that runs just before the :Destroy() in the ModuleScript..
function()
script.Parent:Destroy() -- when commented, these also can resolve the lag. But I don't want that!
script:Destroy() -- this line too.
print("Destroyed")
-- this code makes the heartbeat output.
local t = os.clock()
for i = 1, 37, 1 do
rs.Heartbeat:Wait()
local newT = os.clock()
print(i, ': ', newT - t)
print(script.Parent)
t = newT
end
end)
I am not a very experienced scripter or developer, and I have no idea why there is lag at all, or even why :Destroy() is causing the lag. I’m even more puzzled that the lag happens slightly after calling :Destroy(), not immediately after. If anyone can help, thanks! I can invite you to collaborate in studio if that would help solve the problem.