Cook determinism with SplineComponent

Hello,

I am trying to address cook determinism issues with some of our data. One of the issues we’re getting is in the SplineComponent which may sometime bHasStreamableTextures set to true and sometimes not because it depends on the LineMaterial and PointMaterial members which are TSoftObjectPtr. However, these may or may not be loaded during cook and are directly driving an impact on bHasStreamableTextures because the UPrimitiveComponent::PreSave function reset the bHasNoStreamableTextures flag when cooking. If those materials are not loaded, they will not be considered for determining if the value of bHasNoStreamableTextures. Inversely, if they are loaded, they will be considered and possibly impact that same value differently.

Has anyone else been getting this problem ? If feel like and easy fix would be to override the SplineComponent’s PreSave method and have it force-load these materials to ensure they are loaded and do so only whenever we’re cooking.

[Attachment Removed]

Hello!

Can you share more of your findings? The soft pointers are both loaded in USplineComponent::OnRegister which runs at cook time so they should alwaus be loaded. Even then, the material referenced, /Engine/EngineMaterials/LineSetComponentMaterial, does not reference any texture so it should not influence the value of bHasStreamableTextures in the end.

Regards,

Martin

[Attachment Removed]

Hi Martin,

Thanks for the reply!

I’ll look into that shortly (it’s a long process as you could imagine due to the cooking process that’s implicitely needed) but thanks for pointing out the LoadSynchronous calls from the OnRegister overload which I had not noticed. So knowing that now, my guess would be that perhaps between the time the OnRegister is called and the PreSave is routed, a GC pass ran in-between and collected these references as they are “soft” ones ?

The logs seems to indicate there was a GC pass done not long before the deterministic report on that asset but I need to confirm that first before calling it root cause.

I have my suggested fix implemented on my side and it does seem to fix this issue.

Hopefully I can undo that fix and recreate these conditions locally and see the relationship between the Register and the Presave. I’ll report back when I get more news.

JF

[Attachment Removed]

Good morning Martin!

I hereby confirm to you my suspicions were right.

I have undone my fix locally and recreated the condition under which this reproduces the problem. While doing so, I’ve set code breakpoints in both SplineComponent::Register and SplineComponent::Presave under specific conditions to meet the few components that meet this race condition. When my breakpoint was hit from the ::Register, I’ve set data breakpoints on the both loaded LineMaterial and PointMaterial UObject* on the objects’ ObjectFlags and ptrs. Once that was done, I’ve hit run to see which would break first between the data breakpoint and the code breakpoint set in the ::Presave. As I suspected, the GC runs next before the Presave is called and collects both of these soft referenced assets such that by the time the Presave is routed to that SplineComponent, both of these assets are nullptr.

This confirms why overloading the Presave in the SplineComponent and call LoadSynchronous on both the LineMaterial and PointMaterial, moment before the PrimitiveComponent’s Presave is determining the value of bHasNoStreamableTextures fixes this issue. (granted it doesn’t exactly fix this issue but narrows the window so much so that I think it’s safe to call it the day with that)

Let me know if you need more details regarding my methodology or this conclusion. Meanwhile, I will keep my fix locally to get rid of these issues.

Cheers,

JF

[Attachment Removed]

Have you checked why bHasStreamableTextures get set to true when the materials are loaded? They don’t use textures so bHasStreamableTextures is not being touched when I test locally. I made sure the materials are loaded and returned from GetUsedMaterials.

I also checked for childs of the SplineComponent but the soft pointers are private so it’s not possible to change the reference from LineSetComponentMaterial.

[Attachment Removed]

I’m not sure I fully understand the question ? USplineComponent::GetUsedMaterials will return both LineMaterial and PointMaterial if they are loaded. If they’re not, it will not return them. UPrimitiveComponent::PreSave’s implementation depends on GetUsedMaterials to determine bHasStreamableTextures’s value.

[Attachment Removed]

bHasNoStreamableTextures is initialized to false at the begining of UPrimitiveComponent::PreSave. If there are no Materials returned by GetUsedMaterials it will early out and therefore never set bHasNoStreamableTextures to true (this behavior is likely a bug btw). If the materials are loaded and GetUsedMaterials returns them, it will go through and indeed, not find any textures that are streamable but it will at least set bHasNoStreamableTextures to true.

Alternatively, we could initialize the value of bHasNoStreamableTextures to true by default but it feels rather assumptive to do so since there are materials and I guess, maybe they might return streamable textures ultimately ? So loading those and let the code flow evaluate normally feels like a better fix ?

(Also not a fan of the “not not” in there with the meaning/naming but that’s another story)

[Attachment Removed]

The “not not” got me… I now understand what is happening. Resetting bHasNoStreamableTextures to true would be the proper fix here. There is no streamable textures unless a texture is detected from the materials.

Having the soft pointer on the SplineComponent is still problematic as the source of non-determinism would be back if the material ever get changed to use a texture.

I’ll try to slip that in 5.8

[Attachment Removed]

ok, it’s a bit trickier than expected. I recommend that you resolve the soft pointer in GetUsedMaterials so that the materials are always returned at cook time.

[Attachment Removed]

Sounds good! I’ll make that recommended change on my side as opposed to overriding the SplineComponent’s Presave to do so. I do think both fixes would be good however (both loading the assets in GetUsedMaterial AND defaulting bHasNoStreamableTextures to true at first in UPrimitiveComponent::PreSave)

[Attachment Removed]