Actors on multiple data layers

I am making a build system where the player can “build” from a selection of pre-placed object sets. I have implemented this using data layers. This is for constructing guard towers and small buildings

The rough idea is that data layer has all the actors associated with the tower, and the player can make a selection in a UI that then triggers the server to load that data layer, thereby loading the object into the world. Instantiating a dynamic level instance isn’t suitable for this, because each buildable “plot” is uniquely anchored and integrated into the environment, and so the only real option is to use data layers that show/hide these collections of actors that make up these buildables. At a basic level, this works fine. I am telling the server to enable the chosen buildable data layers, and they load.

As an additional user experience level to this, I also wanted to use client only data layers, to facilitate client side previews of the objects that will be constructed, so the client can visualize a 3d preview of the result, before actually committing to it. This is where I am running into what seems like a bug.

To avoid data bloat and keep things simple, I include all the same actors from the authoritative data layer(unfiltered data layer), in a client only data layer. The idea is that at least on the client side, the client can load the actors via the client only data layer, and the server can load them for everyone using the other unfiltered data layer.

But this doesn’t work as I expect. If an actor is in a client only data layer, it will not unload, even if you have both data layers unloaded.

Tested the following

  • Actor is just in a single “buildable” data layer. loads/unloads fine with the runtime state of that data layer properly
  • Actor is in a client only data layer only. No other layer. loads/unloads with client only data layer properly
  • Actor is in both the “buildable” data layer and a client only data layer. The actors are loaded completely irrespective of either data layer load state. Both layers disabled? still loaded.

Using the default EWorldPartitionDataLayersLogicOperator::Or mode, so I expect that actors would only be loaded if either of these data layers is loaded, but something about being in both is causing the whole set to be always loaded.

Some extra I think relevant information. The buildable layer is under a “plot” layer that acts as a grouping layer for all child buildable data layers, like so. There’s only 1 buildable under this for right now, but the idea is that there could be many, any one of which(or none at all) could be activated.

[Image Removed]

The client only data layer is not in a hierarchy, since you can’t parent them under non client data layers, so they exist at the root of the data layer tree

[Image Removed]

Edit: This seems to be maybe indicative of what is working unexpectedly. I added some debug to our netimgui window so I could visualize the world partition cell state and found an interesting result. Filtered to the relevant layers. All layers disabled by default

In this case, the actors don’t share the Buildable_00 and ClientBuildHint_00. I put a unique single actor on the client hint layer

The result is as you would expect. The Buildable_00 layer has all the buildable actors, independent of the singular actor in the build hint layer.

[Image Removed]

But look what happens if I take that singular actor from the ClientBuildHint_00 layer, and also add it to Buildable_00 layer. The client build layer disappears entirely.

[Image Removed]

Repeating the test again, but filtering on the specific actor to see what cell/layers it ends up in.

Singular layer association looks as expected

[Image Removed]

But again also adding it to the 2nd layer, and it gets assigned to a layer that isn’t even associated with a data layer at all. So it looks like actors associated to dual filters basically get moved to non data layer collections.

[Image Removed]

So it appears the issue is that the issue is due to some logic specifically associated with an actor that is part if differently filtered data layers.

Membership in multiple client only layers looks as I would expect

For client only layers

[Image Removed]For unfiltered layers

[Image Removed]

So I guess the question is. Is there some unknown problems that I am not aware of that keep this actor from being assigned to layers with different filters? I would expect the actor to show up in a cell associated to Buildable_00 and ClientBuildHint_00 data layers.

This seems like a clear bug. Or something that should at least throw a warning or something.

I can see in UWorldPartitionRuntimeHashSet::GenerateStreaming that it looks like the implementation expects that any given runtime cell will be SetClientOnlyVisible in its entirety, which leads me to believe that the implementation effectively doesn’t support data layer combinations that aren’t entirely client side or entirely not client side. It can’t support something that is mixed as I am trying to do.

Is this just an implementation oversight, or is there some deeper reasons why cells must be either all client only , or all non client only? I gather by looking at other code that it allows some performance minded short circuits when considering cells, but it also prohibits this sort of use case for reasons that are not clear.

Thoughts?

Edit: I dug around enough to see that there is an error being logged related to this configuration, with a call to ITokenizedMessageErrorHandler::OnDataLayersLoadFilterMismatch.

[Image Removed]Notice how this particular message is the only one in the file that is missing the call to

[Image Removed]So this erroneous setup was masked by a bug in the error reporting.

Adding line missing line sure enough fixes the missing mapcheck reporting

[Image Removed]

So that sheds some light on the fact that the implementation has a notion of incompatible load filters.

I guess that leaves me with the only question of

Why can’t actors span different load filters?

Hello,

I apologize for the delay. Good catch on the missing error message, and we’ll get that fixed!

As for the reasons why this is unsupported, I’ll need some additional time to report back on this after discussing it further with a colleague familiar with the area. Currently, I’m thinking it may be because the behavior is unclear. For example, since an actor only belongs to a single streaming cell/level, which is created based on the unique combinations of data layers that the actor is in, what would be the expected behavior when its multiple data layers with different load filters are activated?

Thanks,

Ryan

After speaking with a colleague familiar with networking about this, I think you will need to duplicate the content for a client-only version, unfortunately. As for workarounds, I can’t say that I’m entirely sure what the best approach might be.

The transformers work on each cell’s associated level. If you have an embedded level instance, then its actors get embedded into its owning world’s World Partition runtime grid cells, which means it will utilize the cell transformers that World Partition is using. If a level instance is standalone, then it will use the cell transformer setup of its World Partition.

-Ryan

Should be in UE5-Main now: cl 43393810.

-Ryan

I was expecting it to work just as it does when an actor is part of multiple data layers of the same load filter. Depending on if you use EWorldPartitionDataLayersLogicOperator Or or And, it would allow a client to load a level, or a server to load the layer.

It’s not a huge deal, it just complicates my use case. Perhaps you can help me with workaround suggestions.

I have

  • EDataLayerLoadFilter::None layer that the server can authoritatively load to apply buildables to the world
  • I want to have client side previews of each of these buildable data sets, before you commit to building them.

At first, I figured this was easy. Add the actors to both an unfiltered data layer, and a client only data layer, so that the client can show the same actors(or a subset of), to preview the buildable, before applying it, spending the resource cost, etc. With the load filter incompatibility, the solution gets way more complex, and potentially burdensome for content creators.

  • I still want to use client only data layers, because there is a lot of expressiveness that comes with using EWorldPartitionDataLayersLogicOperator::And, and data layers are the only pathway to accounting for data layer combinations, streaming cells.
  • I’d like to avoid or minimize a bunch of extra manual work on the part of the content creator, to maintain the client only data layer actor set. Manual work like cloning actors in place just to include in another layer.
  • I spent a little time experimenting with UWorldPartitionRuntimeCellTransformer, to see if I could utilize that to do a non destructive, end of pipeline post processor, but those aren’t even called with any knowledge of their own levels data layers, much less give you access to the actors of other data layers. If I had the right access, I’d make a UWorldPartitionRuntimeCellTransformer that copied an ISM optimized set of visuals from the corresponding build layers
  • Instead, looks like I basically need to script up a tool that copies a subset of the buildable data layers into standalone actors on the buildable preview data layers.

Separate question about UWorldPartitionRuntimeCellTransformer

  • How does UWorldPartitionRuntimeCellTransformer work along side the ELevelInstanceRuntimeBehavior modes?
    • Do embedded level instances effectively utilize the runtime cell transformers of the container level? Do they also run their own?
    • Do standalone level instances effectively utilize the runtime cell transformers defined within them?

Sorry for the bump, but just want to make sure this doesn’t fall through the cracks

ue5-main and the latest 5.6 release neither have the fix to ITokenizedMessageErrorHandler::OnDataLayersLoadFilterMismatch mentioned above

[Image Removed]