How to regenerate a map without lagging

I’m trying to make a map system that regenerates after the round is finished, issue this there’s a huge lag spike when this is happening.

Now it’s very obvious why this is happening, I’ve checked and the script is deleting around 8000 parts at once and spawning 8000 parts at once, how do I make it so this doesn’t lag.

I’m thinking about using a loop, but task.wait() is too slow.

local function regenerateMap()
	workspace.Map:ClearAllChildren()
	
	workspace.Garbage:ClearAllChildren()
	
	roundValue.Value = false
	eventValue.Value = ""
	
	local neighbourHoodClone = neighbourHood:Clone()
	neighbourHoodClone.Parent = workspace.Map
end
1 Like

Streaming enabled.
It won’t have to update every change to a client if updated area is not streamed.

Still lagging, in fact I think it made it lag even more

I just realized, it doesn’t lag when the map is intact.

I have this “event” that fires missiles that unanchors many of the parts.
If those parts are unanchored, it lags

If it’s the same map, then you could just try to parent it into ServerStorage or somewhere where the players cannot see it, then parent it again to Map instead of just destroying it

Issue is it has to be regenerated and copied from scratch, as in this game there are events that damage the map

local function regenerateMap()
	workspace.Map:ClearAllChildren()
	
	workspace.Garbage:ClearAllChildren()
	
	roundValue.Value = false
	eventValue.Value = ""
	
	local NewModel = Instance.New("Model")
	NewModel.Name = neighbourHood.Name --Create and name a new model
	
	local Iteration = 1
	for _, v in pairs(neighbourHood:GetChildren()) do --Iterate all map parts
		local Clone = v:Clone()
		Clone.Parent = NewModel
		if Iteration % 100 == 0 then task.wait() end --Every X iterations, wait a bit
	end --Change the number after % to change amount of parts loaded each frame

	NewModel.Parent = workspace.Map --Parent the new model
end

What this does differently is it waits every X cloned parts, allowing the server to rest.

This might not work if you have some objects that are direct children (not descendants) that are welded to each other or connected in some other way.

I’ve created a system that tackles this exact problem! Within the environments of my game, I have destructible structures that respawn once being damaged enough. Using :Clone() and :Destroy() was causing some significant performance hits, so I decided to create some custom logic.

I learnt that it’s much more performant to re-parent instances to hidden datamodels (server/replicatedstorage) as opposed to destroying and cloning them. It’s fast for the server to move workspace objects into ServerStorage, but this still causes some performance hits to clients as those instances are removed from the client’s memory (correct me if I’m wrong here engineers), and will need to be re-downloaded once returned to the workspace.

The most performant method is to re-parent workspace instances to ReplicatedStorage, as then it is always held in memory to both client and server. Since my game like yours has game mechanics that affect the map, e.g. changing the appearance, location and other properties of the parts, these will need to be tracked when the map is first inserted, and re-set once needed.

This rather extreme demonstration has over 13,000 parts with just a very brief lowering of frames (as opposed to half a second of freezing):


Regeneration logic:

  1. Map is created - track all necessary parts (for my system, this is just the destructible structures marked with a ‘Respawnable’ tag)
  2. For each structure, gather all BaseParts and store their original properties in a dictionary:
    • Color, Material, MaterialVariant, CFrame, Transparency, Anchored, etc.
  3. When the part is “destroyed” (DON’T actually use :Destroy()), move it to a temporary folder in ReplicatedStorage.
  4. Get the children of the “destroyed” part and remove the child instances that did not exist to start with. (E.g. temporary effects, decals, particleemitters that were applied)
  5. When the structure needs to be regenerated, return all linked parts to their original parent and restore all original properties.

The very interesting and cool thing about this is that welded unanchored parts still have their functioning welds! Since no instance was destroyed, the Weld.Part0 and 1 are still preserved, so simply re-parenting them and setting them to the original location makes it seem like nothing happened. And there is no weird physics behaviour when the parts are temporarily moved into replicatedstorage as those welds are no longer simulated.

4 Likes

So something like this?

At first, I loop through the map model and store the essential properties in a table

Then after the round, I loop through this folder, get every part in temporary folder, look for matches then add them back?

1 Like

vas?

split the clear-up logic into sections, rather than cleaning everything at once, clear only like, 1000 parts at once every x seconds

local partsToCleanPerInterval = 1000
local cleanInterval = 0.1

local function Clear()
   local partsToClean = someFolder
   local cleaned = 0
   
   for i = 1, #partsToCleanPerInterval do
     partsToClean:GetChildren()[i]:Destroy()
     cleaned += 1
   end

   if #partsToClean:GetChildren() > someThreshold then
 -- recursively call if not enough parts were cleaned
     Clear()
   end
end

Yes! You’re taking into account all of the map’s parts STARTING properties & original parents, then when your round ends, you’re setting those back to their original :slight_smile: It works surprisingly well when you have it set up correctly.

Here it is set up with a custom worldfallheight script (if it’s possible that your environment can fall into the void, this could screw up your regen logic as the object becomes destroyed automatically down there - so I removed the worldfallheight and added my own detection.)

4 Likes

I’ve gotten it to work but I found I could just use the temporary folder and copy the part’s properties from there or just put the part back,

The parent property doesn’t matter much for me, should I js do that instead?

1 Like

Some really good suggestions in here but I’ll also drop a simple one that is very effective and related to this

Just this function instead of task.wait()

-- computebudget is a measure of how long in seconds you are allowed to run between yields
local COMPUTE_BUDGET = 1/60 --Don't want it to exceed more than a full frames worth of time, adjust this to preference, the smaller the number the slower the algorithm that uses it, the bigger, the faster but eventually will cause lag

local lastwait = tick()
local function smartWait()
    if tick() - lastwait >= COMPUTE_BUDGET then
        task.wait()
        lastwait = tick()
    end
end

But with this you can smartWait() after every part and it will only wait when it actually has to.

1 Like

Whatever works for you! I rely on re-parenting to the original parent due to my map’s unique folder hierarchy setup.