ContentProvider:PreloadAsync has an extremely high failure rate when using Content strings

tl;dr

When using Content strings, the failure rate of ContentProvider:PreloadAsync is extremely high. When using Instances, the rate of failure is very low, though there are problems with preloading Instances 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 Instances) 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 Instances, we need to wait until the Instances 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 Instances 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 Instances 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?

6 Likes

The workaround of sending down asset type information, creating corresponding instances on the client, and then destroying them after preloading seems to work.

I don’t think you should mark a workaround as a solution - I believe this is still a bug unless an engineer says otherwise.

1 Like

Sorry for the confusion :sweat: The documentation indeed needs a lot more clarification. I’m going through the process to update it.
When given Content ID strings, PreloadAsync assumes them to be images. Non-image assets will fail when preloaded this way, as seen in your repro. It’s surprising to me that image Content IDs also had a higher failure rate though; though there are a few differences between loading an image as a Content ID vs. as an Instance, I think the failure rate should be similar. Could you confirm that the callback provided to PreloadAsync is returning failure more frequently for image Content IDs?

I don’t believe there’s currently a better workaround than the one you have with creating a temporary Instance. The reason that PreloadAsync needs to know the Instance type is that there is Instance-specific loading logic. For example, the same image asset may be loaded in a different format when loaded in an ImageLabel vs. a Decal.
Thanks for bringing this issue with RemoteEvents and PreloadAsync to our attention though. There is definitely a lot of room for improvement for PreloadAsync and it’s great to hear about your use case

7 Likes

Yeah just learned today that PreloadAsync hasn’t been working the way I thought it was for a very long time. Happy I stumbled apon this now to confirm that

4 Likes

The failure rate increase was probably just the non-image content strings failing. I haven’t been able to reproduce failures loading textures with either Instances or content ID strings.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.