tl;dr
When using Content
strings, the failure rate of ContentProvider:PreloadAsync
is extremely high. When using Instance
s, the rate of failure is very low, though there are problems with preloading Instance
s that haven’t fully streamed in yet.
I’m working on the game CodeCombat Worlds. We have a system that allows us to query the most important Content
strings (or Instance
s) and send those using a RemoteEvent
to the client to be preloaded. The extremely high failure rates of loading Content
strings with ContentProvider:PreloadAsync
limits the preloading performance and the ways we can preload data.
Why is this a problem?
Only being able to preload extant and streamed-in Instances hurts preloading performance and limits the types of preloading we can perform.
Our server can quickly determine what assets are important for a player to load and then send those Content
strings using a RemoteEvent
, even if the client has not streamed in an Instance (or a corresponding Instance
doesn’t exist yet.)
However, if we send a RemoteEvent
using an array of Instance
s, we need to wait until the Instance
s and their contents have been streamed in around the player before sending the RemoteEvent to preload them. If we send a RemoteEvent
to the client containing an Instance
reference that hasn’t been replicated on the client, that reference will be nil
and we can’t preload anything about that instance. This necessitates introducing a delay before sending the RemoteEvent
which necessarily lengthens the loading time.
I have also noticed an issue where if we send the list of Instance
s really fast with limited streaming settings before those models have streamed in, ContentProvider:PreloadAsync
appears to halt and never succeed, requiring a timeout to not load forever.
The lack of ability to use Content
IDs also prevents more complex preloading schemes where we load assets for Instance
s that do not yet exist but will in the future. For example, if we are very sure that a player was about to “teleport” (CFrame) into a world which has not been instantiated yet, we could load the content IDs for that world and start preloading in the background. This is not possible if Instances
are required to do the preloading.
Reproducing The Problem
Our code for preloading looks like this:
ContentProvider:PreloadAsync(
assetIdsToPreload,
function(contentId: string, assetFetchStatus: Enum.AssetFetchStatus)
if assetFetchStatus == Enum.AssetFetchStatus.Success then
print("Preloaded asset", contentId)
elseif assetFetchStatus == Enum.AssetFetchStatus.Failure then
warn("Failed to preload asset", `"{contentId}"`)
elseif assetFetchStatus == Enum.AssetFetchStatus.TimedOut then
warn("Timed out while preloading asset", contentId)
end
end
)
When using Content
IDs, out of 14 assets, only a single TextureID was loaded. 12 out of the 14 assets are Mesh IDs and 2 are textures. This failure rate is consistent amongst Studio and a test place on Roblox in production.
10:45:50.122 Failed to preload asset "rbxassetid://14188693284" - Client - LoadingUtils:41
10:45:50.122 Failed to preload asset "rbxassetid://14310655215" - Client - LoadingUtils:41
10:45:50.122 Failed to preload asset "rbxassetid://14482605361" - Client - LoadingUtils:41
10:45:50.122 Failed to preload asset "rbxassetid://14482605321" - Client - LoadingUtils:41
10:45:50.122 Failed to preload asset "rbxassetid://14603332715" - Client - LoadingUtils:41
10:45:50.123 Failed to preload asset "rbxassetid://14679220023" - Client - LoadingUtils:41
10:45:50.123 Failed to preload asset "rbxassetid://14482605323" - Client - LoadingUtils:41
10:45:50.123 Failed to preload asset "rbxassetid://14199741323" - Client - LoadingUtils:41
10:45:50.123 Failed to preload asset "rbxassetid://14310659545" - Client - LoadingUtils:41
10:45:50.123 Failed to preload asset "rbxassetid://14482605343" - Client - LoadingUtils:41
10:45:50.123 Failed to preload asset "rbxassetid://14482605324" - Client - LoadingUtils:41
10:45:50.123 Failed to preload asset "rbxassetid://14188692655" - Client - LoadingUtils:41
10:45:50.123 Failed to preload asset "rbxassetid://14679217747" - Client - LoadingUtils:41
10:45:58.221 Preloaded asset rbxassetid://14188694094 - Client - LoadingUtils:39
When I swap this out to preload the Instances from which those asset IDs were calculated, and introduced a delay before sending the RemoteEvent
to the client with a list of those Instances
the failure rate is 0 for those exact same assets.
10:59:14.969 Preloaded asset rbxassetid://14482605324 - Client - LoadingUtils:39
10:59:14.969 Preloaded asset rbxassetid://14482605361 - Client - LoadingUtils:39
10:59:14.969 Preloaded asset rbxassetid://14482605343 - Client - LoadingUtils:39
10:59:14.969 Preloaded asset rbxassetid://14482605321 - Client - LoadingUtils:39
10:59:14.969 Preloaded asset rbxassetid://14188692655 - Client - LoadingUtils:39
10:59:14.969 Preloaded asset rbxassetid://14603332715 - Client - LoadingUtils:39
10:59:14.969 Preloaded asset rbxassetid://14188693284 - Client - LoadingUtils:39
10:59:14.969 Preloaded asset rbxassetid://14482605323 - Client - LoadingUtils:39
10:59:15.068 Preloading done after 5.82 seconds - Client - LoadingTiming:98
10:59:15.068 Loading screen hidden after 5.82 seconds - Client - LoadingTiming:105
10:59:18.253 ▶ Preloaded asset rbxassetid://13805841956 (x2) - Client - LoadingUtils:39
10:59:19.651 ▶ Preloaded asset rbxassetid://14188694094 (x2) - Client - LoadingUtils:39
Note that without the delay before sending the list of Instances
with a RemoteEvent, ContentProvider:PreloadAsync
hangs, likely due to replication issues involving StreamingEnabled
.
Hypothesis
Back in 2017, @programeow posted that PreloadAsync
only supports image IDs when passing Content IDs. Our only successful preload was a texture, but because we had other textures which didn’t load, I suspect this isn’t the issue. The PreloadAsync
docs also don’t suggest that you can only pass in images.
@the_swegmeister also posted about this in July regarding ImageLabels. He said that you need to create an ImageLabel with the correct ID and then pass that to ContentProvider:PreloadAsync()
and that the docs are out of date.
I think this just comes down to Content IDs largely not being supported, and it being undocumented. All the docs say are this.
Yields until all of the assets associated with the given Instances or Content strings have loaded. This can be used to pause a script and not use content until it is certain that the content has been loaded into the experience.
When called, the engine identifies links to content for each item in the list. For any of the Instances which have properties that define links to content, such as a Decal or a Sound, the engine attempts to load these assets from Roblox. For each requested asset, the callback function runs, indicating the asset’s final AssetFetchStatus.
If any of the assets fail to load, an error message appears in the output. The method itself will not error and it will continue executing until it has processed each requested instance or asset ID.
If we can confirm that Content IDs are largely unsupported (or the exact ways that they are supported) I can quickly make a PR on the open source Creator docs.
Potential Workarounds
The main workaround that I can hypothesize to get Content ID like functionality is to send a map of the type of instance the ContentID corresponds to with each Content ID, and then create an instance for that on the client, pass the created instance to ContentProvider:PreloadAsync()
and then destroy it when the preloading is done. Is this it the best or only way?