Using insert service to optimise your game

This is my first tutorial on the devforum so hopefully its helpful.

In this post Im gonna be talking about insert service and how you can use it to optimise your game.

What is insert service?

Insert service is a service on roblox that allows you to load assets that are public or that you own.

This can include models, meshes, sounds and more.

How can you use it to optimise?

Unlike storing assets in replicated storage or server storage, assets that have not yet loaded in the game will not take up any memory. This means we can load assets only when needed and destroy them to free up memory.

Bare in mind that when you load in an asset and destroy it, there will still be cache related to it that will allow the asset to load in faster next time. This cache will take up a little bit of memory and I’m pretty sure you cannot remove it.

An example of using insert service would be if we had a number of maps in our game that could take up lots of memory if we keep adding more.

Usually people will store them in replicated/server storage and then parent 1 map to workspace when needed. This is a less effective way than using insert service to load 1 map when needed and destroying it when its not.

Additionally, Phantom Forces uses insert service to load their maps.

Unfortunately, insert service can take some time to load in unlike storing them in replicated/server storage as it has to load them in from the servers instead of just reparenting. It would be a good idea to load them in a bit before when the player needs them.

Here is a speed comparison for loading using a model that contains 10K parts with the same size but a variety of colour and materials:

Loading the asset then parenting to workspace:
150-160ms average

Parenting the asset from server storage to workspace:
29-31ms average

Parenting the asset from replicated storage to workspace:
23.5-24.5ms average

It is around 5.16x faster to reparent from server storage and 6.46x faster to reparent from replicated storage than using insert service to load an asset.

Even though it is the most important benchmark, I couldn’t get a memory test to work.

How do you use insert service?

Since it is a service, you must use game:GetService("InsertService") in order to access it.

""

You should ideally store it in a variable like this: local insertService = game:GetService("InsertService")

Now lets try and load an asset into a game.
To load an asset, you must get the asset id of the asset. You can do this by going on the toolbox and then your inventory. After you have found your asset, right click it and click “Copy Asset ID”

Now that you have the asset ID, we can load the asset into our game.
The function we can use for loading assets into our game will be insertService:LoadAsset().

It will return a model containing everything in our asset.

This function takes in the asset ID to load in. You can store the asset ID in a variable and write it into the parenthesis or you can paste the ID straight into the parenthesis.

If you need to do anything with the model (such as reparenting, renaming etc), store it in a variable.

Your code should look something like this:

local insertService = game:GetService("InsertService")
local assetID--paste in the assetID here

local model = insertService:LoadAsset(assetID)

Although the asset has loaded in, you will need to parent it somewhere as it does not have a parent by default. Also, the model will just be called “Model” so make sure you rename it if you need to.

Now let’s make our code more secure. When loading an asset into our game, the server might have problems which is why we must wrap some of our code into a pcall and repeat it until it successfully loads.

if you do not know how to use a pcall, i recommend researching on them as they are quite important.

The line of code that you should wrap the pcall in is this:
local model = insertService:LoadAsset(AssetID)

Before we wrap it up though, we will need to predefine our model to make it global so that we can modify it when loaded in.

Add these variables in right before the line of code where we load our asset in:

local model
local success

Now lets wrap our line of code into a repeat loop and pcall.
Wrapping it in a pcall and repeat loop should look like this:

repeat task.wait() success, model = pcall(insertService.LoadAsset, insertService, AssetID) until success

Now this should be the final results of our code:

local insertService = game:GetService("InsertService")
local assetID--paste in the assetID here

local model
local success

repeat task.wait() success, model = pcall(insertService.LoadAsset, insertService, AssetID) until success

Now that we have gone through all of that, we finally have our block of code that can be used to load assets in. Personally, i use a module script and store it in a function that takes in an asset ID and returns the model so other scripts can load in an asset with 1 line of code.

Problems with insert service

Unfortunately, insert service does have cons that storing assets in game do not.

Firstly, the least problematic one, is the time it takes to load.
Its not too hard to solve as you can just load the asset in a little before the player needs to see it or you can add a loading screen.

The biggest problem though is that it changes how you code.
Because the asset isnt always in your game, you cannot predefine assets in your code as they do not always exist. This can lead to major changes in your code and can elongate the amount of time it takes to create a game as you will spend time optimising.

Do I need insert service for optimising?

It depends on how hardware intensive your game is. If you have a game that takes up less memory than the devices you are targeting, you might waste time optimising with insert service when they can already run it fine.

If your game takes up a worrying amount of memory that causes the devices you are targeting for to run out of memory then insert service would be a good idea to use for optimisation. It can lead to significant reductions to memory usage when used correctly.

Bear in mind that not everyone can use insert service to optimise their games, especially a lot of multiplayer ones.
If you have a simulator that contains a variety of worlds, you can’t destroy and load them depending on where the player is as it would affect the other players.
Instead, you should probably enable streaming enabled, though it won’t give all the benefits of insert service.

For linear story games or any single player games, insert service can be used to its full potential for optimisation.

Let’s say there’s a chapter where you are in a massive house and another where you’re in a cave. You could destroy the house and load in the cave once the player has progressed to the next chapter.

Maybe if some characters had dialogue when you talk to them, you could load in all the audio and animations for the character and destroy it when the dialogue is finished.
You would probably only need this if you had a very large amount of audio and animations though.

Thanks for reading my tutorial on insert service. I hope this helps any developers in need of optimisation! Let me know if there are any problems with the tutorial or if there was any information that was wrong or I should have included.

2 Likes

Can we see some benchmarks? Memory usage, processing speed, network usage

2 Likes

I don’t see why you use GetLatestAssetVersionAsync alongside LoadAssetVersion, LoadAsset will load the newest version regardless

also im pretty sure this part can error

local latestAssetID = insertService:GetLatestAssetVersionAsync(assetID)

you dont need that though, just use LoadAsset

local function load()
	local s, result = pcall(function()
		return insertService:LoadAsset(assetId)
	end)
	
	if not s then
		task.wait(.1)
		return load()
	else
		return result
	end
end

local asset = load()

Your usecase is also pretty bad - 1gb memory is completly fine, and how you describe it, you load necessary rooms but unless they’re absolutely massive to a point where having all the sections brings down your framerate/drastically increases memory in studio, you can just reparent the sections from ServerStorage and have them be streamed to the client. You would get the same effect with way less effort. You shouldn’t really care about an extra 100mb memory usage on the server, especially if the way to combat it is adding load times

other than that, this is a pretty good idea if you have a crap ton of content you want to pack in a somewhat seamless expierience

2 Likes

Thanks for the feedback. I probably should have put some notice on LoadAssetVersion() as I wasn’t actually fully aware of its purpose. I’ll be sure to change that when I’m on my pc.

As for my purpose that I used, I’ve realised that it was redundant since they didn’t really take up much memory anyways.

I’ll try to include some next time on my pc. Thanks for telling me.

Recursive is bad for perfomance and memory safety
Post is about perfomance so i optimized your code a little bit.

local InsertService = game:GetService("InsertService")
local RunService = game:GetService("RunService")
local Heartbeat = RunService.Heartbeat
local LoadAsset = InsertService.LoadAsset


local function load(assetId:number):Model
	local s, result = pcall(LoadAsset,InsertService,assetId)
	while s == false do
		Heartbeat:Wait()
		s, result = pcall(LoadAsset,InsertService,assetId)
	end
	return result
end

local asset = load(491824812984898--[[I put a bunch of random numbers make sure to replace them]]])
2 Likes

Completely forgot to add a wait to the repeat loop thanks for reminding me

Also I believe task.wait() does the exact same thing as Heartbeat:Wait()

This is the loader, it makes 0 difference, once it finishes running everythings gets garbage collected, but if you really dont want recursion then just do:

while true do
	local s, result = pcall(LoadAsset, InsertService, assetId)
	if s then return result end
	task.wait()
end

Task.wait does more checks there so has more perfomance overhead
Using heartbeat for yielding in this case would be faster

Your example is not really good perfomance wise…
It creates local everytime and initiates loop even through in 99% cases it would be avoided entirely.
Also task.wait has perfomance overhead compare to heartbeat event

Okay thanks for the information on heartbeat

source

source
your code is no better, you’re just making a local outside the loop instead of in the loop, which barely does anything

4 Likes

wdym source?
You are incapable of reading the code?

source where your claim is true

you’re incapable of reading this?

2 Likes

benchmark.

Mutating a variable would obviously be faster and task.wait being slower than a rbxscriptconnection yield is a common sense, no benchmark needed

please show us your benchmark, dont say its a sub us difference

we often follow our intuition blindly without a source of truth

emphasis on “barely does anything” please :pray::pray:

2 Likes

Sure.

Heartbeat is infact faster than task.wait by a lot if caching signal class.
From my experements benchmarking InsertService is very annoying as since needing to wait about 1 minute for limitations to go awey and my method is faster btw.
Also i changed code a little bit
image

Code
--!strict
--!optimize 2
local InsertService = game:GetService("InsertService")
local RunService = game:GetService("RunService")
local LoadAsset = InsertService.LoadAsset
local Heartbeat = RunService.Heartbeat
local Heartbeat_Wait = Heartbeat.Wait


local function WhileLoop(assetId:number):Model
	while true do
		
		local s, result = pcall(LoadAsset, InsertService, assetId)
		if s then return result end
		task.wait()
	end
end

local function load(assetId:number):Model
	local s, result = pcall(LoadAsset,InsertService,assetId)
	if s then
		return result
	else
		while true do
			Heartbeat_Wait(Heartbeat)
			s, result = pcall(LoadAsset,InsertService,assetId)
			if s then return result end
		end
		return result
	end
end


local t:number = 0



t=0
for i=1,3 do
	local a = os.clock()
	for ii=1,2 do
		load(121120339479642)
	end
	t+=os.clock()-a
end

print(`My code: {t/20}`)
task.wait(5)

t=0
for i=1,3 do
	local a = os.clock()
	for ii=1,2 do
		WhileLoop(121120339479642)
	end
	t+=os.clock()-a
end

print(`WhileLoop code: {t/3}`)

your test relies on your internet connection though? this is not a valid benchmark

run it on your machine then.
I aint stopping you from doing that and showing the result
Also it takes avarage result out of 3 iterations so its pretty accurate

blud doesnt get it
your internet connection isnt always the same, its affecting the result of each test

othen than that, this optimization is completly pointless. even a 1/10th of a second difference between each load attempt isn’t going to be noticeable, you’re just writing longer code, ontop of that - we’re adding the yield as to not continously spam it, if you want to wait shorter between each attempt, just dont yield at all?

1 Like