sinbad / SPUD

Steve's Persistent Unreal Data library
MIT License
338 stars 51 forks source link

Disabling SPUD persistence on dynamically spawned level instances #94

Closed Olaxan closed 1 month ago

Olaxan commented 1 month ago

Hi! Big time SPUD fan here, it has served us very well during development of our game, and we've yet to encounter significant issues.

We're implementing a procedural part of our game, using dynamic level streaming (a classic dungeon generator). Essentially we clone level instances to form a tiling dungeon, I believe it's pretty standard stuff.

The level streaming is performed using this method:

ULevelStreamingDynamic::FLoadLevelInstanceParams params(GetWorld(), data->Level.GetLongPackageName(), transform);
params.bLoadAsTempPackage = true;
params.bAllowReuseExitingLevelStreaming = true;
params.bInitiallyVisible = true;

bool success = false;
ULevelStreamingDynamic* level = ULevelStreamingDynamic::LoadLevelInstance(params, success);

The issue that arises is that each cloned instance receives a unique name (akin to LV_Tile_Shortcut_Staircase_NSE_LevelInstance_250. As a result, the following is immediately shown in the Output Log:

LogSpudSubsystem: Verbose: Level shown: LV_Tile_Shortcut_Staircase_NSE_LevelInstance_250
LogSpudState: Skipping restore level LV_Tile_Shortcut_Staircase_NSE_LevelInstance_250, no data (this may be fine)

However, the level, and all its actors, is definitely persisted upon de-spawning. De-spawning is done with:

level->SetIsRequestingUnloadAndRemoval(true);

I imagine that it will never be able to use the persisted data, because the level is dynamic. This makes me worry about save file bloat, and also causes unnecessary stutters when creating tiles for the dungeon.


My question is whether or not it's possible to exclude these sorts of dynamically spawned levels from the persistence system. There are some parts of the ULevelStreamingDynamic system that I feel could be used to identify them, such as bLoadAsTempPackage, OptionalLevelNameOverride, and OptionalLevelStreamingClass -- but I'm not sure where to start.

Before I dig into the SPUD logic to try to jam these in there myself, I thought I'd ask to see if this type of behaviour is already supported, or if there is a logical place to implement it.

Thanks in advance (and thanks for a great plugin)!

sinbad commented 1 month ago

Hi, excluding entire levels from the save/load sequence isn't something I'd considered before.

If you were to do this, you'd have to identify the levels to exclude solely from the ULevel class, since that's all we have to work with in practice. SPUD only sees levels once they're loaded / just before unloading, which is a level below the ULevelStreamingDynamic system (that merely organises everything, it just becomes a ULevel at the end of the day). See the calls to USpudSubsystem::StoreLevel and USpudSubsystem::RestoreLevel. Sadly I don't think there's that much to go on, barring perhaps a LevelName wildcard exclusion match or something.

Olaxan commented 1 month ago

Thank you, Steve! What I ended up doing was create an actor component USpudLevelDataComponent, which I could attach to my AWorldSettings. I added a field bPersistInstances to the component, and some simple additions in SpudSubSystem to skip level instances from being serialised if that property is false in the world settings of the current world. I suppose I could refine the idea to specify behaviour on a per-instance level, but for now it's fine to just skip them all.

Thanks for the pointers in the right direction! I'll close the issue.

sinbad commented 1 month ago

OK, I think I'll add exclusion settings to the system anyway so people can more easily exclude levels in future, it sounds like a useful feature.