A faster, more efficient loading system

For most of my loading, I use a simple for loop to clone all the children of a map and parent it to the workspace, waiting for a moment to prevent lag like this:

for i, child in pairs(children) do
  local new = child:Clone()
  new.Parent = workspace

  wait()
end

However, a significant problem with this is the wait time. Bigger maps will take longer to port into the workspace. One of my one taking over 30 seconds to do so.

How would I go about making a more efficient/faster loading system? Some examples could be the one in Flood Escape 2, with the maps taking a few seconds to load to almost instantaneous.

2 Likes

Use unions or meshes to reduce the number of parts, and clone the whole model instead of one-by-one cloning each child?

1 Like

Rather than looping over children, clone the whole object in one go and parent it.

The next best thing is to loop over each child and parent it to a Folder or Model in nil, and then parenting that Folder or Model to the workspace. This is still a good bit slower than just cloning the whole thing and parenting it.

The reason is actually pretty intuitive I think. Basically, every time you set the parent of an object, Roblox will cause an event, and, it will collect some information about what you are parenting to give to the client. Roblox will then send that information on the next frame, and if the client doesn’t have the instance already it will send all of the data about the instance.

By cloning and setting the parent of each object individually, you cause those events and you cause Roblox to send lots of new data lots of times. If you parent only one object, you only create those events once, you only send the entire instance once, and, since there is a bit of extra data that has to go with each object the amount of data Roblox has to send and process is a lot lower.

If your scripts require everything to be in the workspace, you should definitely edit them and use a variable to decide where to look for map objects, that way you can change it easily if you want to. Usually I would recommend never parenting directly to workspace and instead using a few Folders or Models to hold lots of instances in. For example, you might have a folder called Map which contains the entire map, and, for dynamic objects which you create at different times you might have, for example, a Vehicles folder which has a bunch of vehicles you can drive.


@nicemike40 Also has a pretty good suggestion, but it can cause a lot of lag if you just merge a bunch of parts into unions. I would only recommend it in some cases since it can cause a lot of lag depending on the shapes you are unioning and how many unions you are creating.

The reason is that Roblox can batch render things more efficiently than it can render a large amount of unions. Unions can also increase the triangle count (but can also decrease it depending on what you are unioning) because, while a part might have 12 triangles, merging two parts could create a situation where 20 triangles are needed to represent the shape without having any intersecting triangles.

And, because unions have mesh data, too many unions can use a lot more memory than a bunch of parts would. There is a balance, you don’t want too many unions, but, you don’t want too many parts.

Somewhat recently Roblox added a feature to view things in wireframe mode. This lets you see all of the triangles in your objects. You can find it under View > Wireframe Rendering:

If you want to spend the time to do this, I would recommend only unioning unique models with a really large amount of parts. This would reduce how much data has to get sent, and, is more likely to reduce the triangle count.

4 Likes

I forgot to mention in my post that there is a wait in the code to prevent mass amounts of lag. Cloning an entire object (especially with the size of the objects I’m cloning) will probably end up freezing the player’s computer for a few frames. Usually I prefer to use RunService.Heartbeat:Wait() to further reduce the time between cloning each object.

If there is data being sent each time an object is being put into the workspace, is there a way to save that data somewhere to reduce waiting time?

Using wait to prevent lag doesn’t actually benefit you at all, I used to do this all of the time, in fact, only three years ago I was still doing that maybe. By using waits, you are making things take much, much longer than they need to, you are essentially adding artificial lag and creating the illusion that there isn’t lag by allowing other things to execute a little too. Either way, its going to take the same amount of time or more, and, usually it takes more. Over time I have learned that wait is usually a really bad solution to most things.

If you have StreamingEnabled on, the load time of instances is already automatically spread out, and, its extremely well optimized. Inserting extra yields and splitting up all of your ancestry changes will both add more lag, just spread out over a longer time, but, will also increase your total time by what could equate to tens of seconds depending on how much stuff you’re parenting. If I’m not mistaken, even without StreamingEnabled, Roblox is no longer loading instances in one go as of about two years ago.

The freeze that people will experience is not worth the amount of time you are losing, people aren’t going to mind a small freeze if they are getting stable FPS and aren’t having to wait forever for things to happen.

You shouldn’t be worrying about freezing that much, and, you shouldn’t ever be using yields except when its actually because you want something to happen after a certain amount of time. For context, my game has a terrain generation system, and, it sends hundreds of thousands of instances at once to the client. The amount of freeze you will feel is so subtle that you will think its just a little bit of lag, and, it lasts for only a second or so.

The one exception to this is when you have a piece of lua code that’s running for a long time. In this case, sometimes I will measure the start time with os.clock() and if the difference between the current time and the start time exceeds two seconds I will put in a Heartbeat:Wait() to stop a timeout. And, occasionally I will have things only execute every so often if it makes sense to.

4 Likes

Tried this out, and it actually worked really nicely. thanks for the suggestions.

Hey, thanks for this post. I just wanted to clarify what you said in the beginning, is it something like this?

local NewMap = game:GetService("ServerStorage").Map:Clone()

local MapHolder = Instance.new("Folder")
MapHolder.Parent = nil

for _, Child in pairs(NewMap:GetChildren()) do
	Child.Parent = MapHolder
end

NewMap:Destroy() --Destroy it because it's an empty folder at this point
MapHolder.Parent = workspace