Hello! I am working on a system that can load in huge maps for my game.
Currently it takes around 3:30 - 4:00 minutes to load in a full map which is a pretty long time.
I am wondering if anyone has any suggestions and ideas on how I can further optimize this.
The script first loads in the nature, structures then misc, all from their folders, at the end it will load the terrain in, here you have an image to visualize it.
code:
function MapFunctions.Load(map)
local NatureFolder = map.Assets.Nature
local StructureFolder = map.Assets.Structures
local MiscFolder = map.Assets.Misc
local TerrainRegion = map.RBLXterrain:FindFirstChildOfClass("TerrainRegion")
local i = 0
local MaxIterationsUntilWait = 100
local StuffToLoad = NatureFolder:GetChildren()
for _, v in ipairs(StuffToLoad) do
if i >= MaxIterationsUntilWait then
i = 0
RunService.Heartbeat:Wait()
else
i += 1
end
local clone = v:Clone()
clone.Parent = WorkspaceMapFolder
RunService.Heartbeat:Wait()
end
StuffToLoad = StructureFolder:GetChildren()
i = 0
for _, v in ipairs(StuffToLoad) do
if i >= MaxIterationsUntilWait then
i = 0
RunService.Heartbeat:Wait()
else
i += 1
end
local clone = v:Clone()
clone.Parent = WorkspaceMapFolder
end
i = 0
StuffToLoad = MiscFolder:GetChildren()
for _, v in ipairs(StuffToLoad) do
if i >= MaxIterationsUntilWait then
i = 0
RunService.Heartbeat:Wait()
else
i += 1
end
local clone = v:Clone()
clone.Parent = WorkspaceMapFolder
end
local position = Vector3int16.new(
-math.floor(TerrainRegion.SizeInCells.X / 2),
-math.floor(TerrainRegion.SizeInCells.Y / 2),
-math.floor(TerrainRegion.SizeInCells.Z / 2)
)
workspace.Terrain:PasteRegion(TerrainRegion, position, true)
local waterProps = TerrainRegion:FindFirstChild("WaterProperties")
if waterProps then
LoadTerrainProperty(TerrainRegion, "WaterColor")
LoadTerrainProperty(TerrainRegion, "WaterReflectance")
LoadTerrainProperty(TerrainRegion, "WaterTransparency")
LoadTerrainProperty(TerrainRegion, "WaterWaveSize")
LoadTerrainProperty(TerrainRegion, "WaterWaveSpeed")
end
local materialColors = TerrainRegion:FindFirstChild("MaterialColors")
if materialColors then
for _,material in Enum.Material:GetEnumItems() do
local colorVal = materialColors:FindFirstChild(material.Name)
if colorVal then
AttemptSetMaterialColor(material, colorVal.Value)
end
end
end
end
1 Like
I recommend using octrees or some chunk system. With octrees, you can load what’s next to you and unload what’s far away. It’s very cheap to get the distance too because you can just check the parent nodes distance. If you do have to load everything try loading everything in parallel by splitting it into different parts and making each actor load those in. For example, have one actor load in nature, another structure, etc. I haven’t used parallel much so I can’t help you with it but I have seen people improve loading times for things like this with parallel. So maybe try experimenting with parallel.
function MapFunctions.Load(map)
local NatureFolder = map.Assets.Nature
local StructureFolder = map.Assets.Structures
local MiscFolder = map.Assets.Misc
local TerrainRegion = map.RBLXterrain:FindFirstChildOfClass("TerrainRegion")
local StuffToLoad = map.Assets:GetDescendants()
-- Iterate over the contents of all three folders
for _, v in ipairs(StuffToLoad) do
local clone = v:Clone(true)
clone.Parent = WorkspaceMapFolder
RunService.Heartbeat:Wait()
end
local position = Vector3int16.new(
-math.floor(TerrainRegion.SizeInCells.X / 2),
-math.floor(TerrainRegion.SizeInCells.Y / 2),
-math.floor(TerrainRegion.SizeInCells.Z / 2)
)
workspace.Terrain:PasteRegion(TerrainRegion, position, true)
local waterProps = TerrainRegion:FindFirstChild("WaterProperties")
if waterProps then
LoadTerrainProperty(TerrainRegion, "WaterColor")
LoadTerrainProperty(TerrainRegion, "WaterReflectance")
LoadTerrainProperty(TerrainRegion, "WaterTransparency")
LoadTerrainProperty(TerrainRegion, "WaterWaveSize")
LoadTerrainProperty(TerrainRegion, "WaterWaveSpeed")
end
local materialColors = TerrainRegion:FindFirstChild("MaterialColors")
if materialColors then
for _, material in Enum.Material:GetEnumItems() do
local colorVal = materialColors:FindFirstChild(material.Name)
if colorVal then
AttemptSetMaterialColor(material, colorVal.Value)
end
end
end
end
Improvements:
- Used the :GetDescendants() method instead of :GetChildren() to get all the children of Nature Folder, Structure Folder, and Misc Folder.
- Used a single
for
loop to iterate over all 3 folders instead of using 3 separate loops.
- Used the :Clone() method with the Create = true option to create the clones on the server, which will be faster and more efficient than creating them on the client and then transferring them to the server.
- Other minor not-notable improvements.
I think you can remove the 2nd heartbeat wait. MaxIterationsUntilWait
should have a value of around 512-1024 depending on your preference, but I think 100 is too small.
In addition, you have many duplicates of this code. This is due to multiple folders being used. You can use GetDescendants
to load everything. However, this might cause issues due to some of the map assets being models. This brings me to the question, why can’t you clone it all at once, without cloning each individual asset? That would probably be much more performant.
Hey!
Thanks for the feedback, the reason I have 3 different folders is to seperate the nature, structures and anything besides that in misc.
Hey I can’t really use :GetDescendants() because then it will put all the children of the house models outside the model.
One optimization that could be made is to use a multi-threaded approach to load in the assets. Instead of waiting for each iteration to complete before moving on to the next one, you could create multiple threads to handle loading in the different folders in parallel. This would allow the assets to be loaded in faster, as each thread would be able to work on its own set of assets concurrently.
Another optimization that could be made is to use a cache system to store the assets that have already been loaded. This way, if the same map is loaded multiple times, the assets will not need to be loaded from the folders again, as they will already be stored in the cache. This can significantly speed up the loading process, especially for large maps.
Finally, you could consider using a progress bar or some other visual indicator to show the progress of the map loading. This can help to keep the player informed about how long the process is taking, and can also provide a sense of progress to keep them engaged.
A multi threaded approach sounds interesting and I may actually use this. But won’t I have to lower the MaxIterationsUntilWait value since it could probably overflow and make the small delay useless?
If you are using a multi-threaded approach, you may need to lower the MaxIterationsUntilWait value to ensure that the thread does not become overloaded. This is because the MaxIterationsUntilWait value determines how many iterations of the loop the thread will run before it waits for a small amount of time. If the MaxIterationsUntilWait value is too high, the thread may become overloaded with too many iterations and may not be able to function properly.
However, you should be careful when lowering the MaxIterationsUntilWait value, as it may also increase the amount of processing power the thread uses. You should try to find a balance between the two to ensure that the thread is efficient and does not cause performance issues.
It is also important to note that the MaxIterationsUntilWait value is not directly related to the issue you are experiencing with the CFrame/Position of the parts not updating. It is simply a tool that can be used to improve the performance of a multi-threaded script.