Additional clarifications around .uplugin exclusion for GFPs

This question was created in reference to: [completely prevent a game feature plugin from packaging at cook [Content removed]

Hi there!

Wanted to get some additional clarifications on exactly how Fortnite leverages TargetRules.DisablePlugins here.

So, we understand the problem well, UBT collects all enabled plugins (GFPs or not) and puts them as runtime dependencies in the target recipe. The stage step in BuildCookRun grabs all runtime dependencies from the recipe (enabled .uplugin files) and stages them for UFS packaging. This means all .uplugins for all enabled GFPs (content and code) will end up in the built artifact even for GFPs that our custom UGameFeaturesProjectPolicies excludes from the build through our custom filtering of what plugin should be in which release.

I’d like to understand a bit more.

Especially for content-only GFPs which are the most common ones, often the .uplugin will be named after some future feature (like <new_hero>.uplugin and the intention is to allow development of the feature without having to worry about shipping it yet. However, due to this packaging wrinkle, the .uplugin itself will leak to players even if the content for it is not cooked.

From the answer to the previous thread it sounds like in FN there exists a C# function that manipulates TargetRules.DisablePlugins to avoid enabling some GFPs that are not meant to leak. However, I’d like to learn more as to how this function interacts with FN’s game feature project policy class. Do you basically have the same logic implemented twice in C++ and C# to guarantee that the same plugins are enabled or disabled both at UBT time and cook time? Where is this custom function implemented exactly?

What’s a bit awkward here is that we are toying with the idea of controlling which GFPs are to be included in the build via custom data assets, and while it is trivial to do that in game feature project policy code, it is awkward to use the same logic at UBT time in C#.

Hey Daniele, I posted an article on EDC on this topic: Build-time Asset and Plugin Exclusion. If you haven’t read it yet I believe it contains some of the answers you’re looking for but not all, so I’ll try to cover the rest here.

To correct my colleague’s answer on that older post: in Fortnite all conditional plugins are opt-in rather than opt-out:

  • In Fortnite all .uplugin files are configured as bEnabledByDefault = false. This is checked when UBT runs the TargetRules class, so handled in C#.
  • In the target rules, a version number in the .uplugin file is compared to that build’s version number to decide whether to add it to TargetRules.EnablePlugins.
  • The example target rules file in that article is a simplified version of what Fortnite’s target rules, but it might serve you well as reference.

Looking at our GF Project Policies class, there is indeed similar version checking logic in ::WillPluginBeCooked(). Is that the function you’re using to exclude GFP assets from a build? I haven’t had the opportunity to work on GF policies directly, so just asking to make sure I understand your current approach correctly.

The bottom-line is that in Fortnite we prevent plugin files from being cooked and staged primarily through UBT target rules. I don’t believe there is a convenient way to access properties from uassets from there - as you mentioned a bit awkward to do in C#. You could consider introducing a UDeveloperSettings class per GFP, since that gives editor-exposed configurable properties that are written to ini files that can be read in C#.

Hey Zhi!

What a nice read, thanks for sharing. It makes sense.

Yeah using a custom UGameFeaturesProjectPolicies seems like it indeed is the intended/preferred mechanism for using logic to determine which GFPs are to be included/excluded in the build at cook time, at least in GFP documentation/literature, but it does seem like in practice Fortnite’s approach is much different.

However, it does feel a bit odd to have the logic both here and in UBT C# code… The tutorial you linked makes sense because if things are done at the C# layers ultimately you’ll have a list of EnabledPlugins that respect the build configuration and the logic we want for inclusion/exclusion. Having the logic here in C++ as well feels a bit redundant since at cook time UGameFeaturesSubsystem::Get().LoadBuiltInGameFeaturePlugins is only looking at enabled plugins anyway (passing your C# checks) … I’d be curious to hear how FN uses its UGameFeaturesProjectPolicies, and why: is it mainly to control which GFPs are runtime loaded among the ones that have been packaged?

Daniele

Glad to share that!

As for how Fortnite uses its GameFeatureProjectPolicies, I’m asking around and will let you know once I know more.

Hi again, reporting back here after having talked to the engineer who worked on large parts of Fortnite’s build systems.

Key overview

UBT, UGameFeaturesProjectPolicies at cook-time, and UGameFeaturesProjectPolicies at run-time each play a part in deciding which plugins are enabled, cooked and loaded. In each step you can filter out more plugins. When it comes to preventing leaking of any plugin files including the .uplugin, that must be done through UBT.

“Yeah using a custom UGameFeaturesProjectPolicies seems like it indeed is the intended/preferred mechanism for using logic to determine which GFPs are to be included/excluded in the build at cook time, at least in GFP documentation/literature, but it does seem like in practice Fortnite’s approach is much different.”

So it turns out it’s not that different but we use each step for specific purposes.

Fortnite does use UGameFeaturesProjectPolicies to control what to cook (as well as what to load at runtime) but fully relies on the UBT step to avoid leaking files by keeping those plugins disabled. On development streams we enable all plugins for editor builds so that developers have access to all assets in the editor, while at cook-time not all of those plugins are cooked. For that we rely on UGameFeaturesProjectPolicies. So while version checking logic is duplicated in UBT and UGameFeaturesProjectPolicies, the output will be different in dev streams. For example, our target rules file has a special case for editor builds to enable all plugins.

“I’d be curious to hear how FN uses its UGameFeaturesProjectPolicies, and why: is it mainly to control which GFPs are runtime loaded among the ones that have been packaged?”

It’s used for both cooking and runtime loading. I’ll explain how we use UBT and the custom policies class in each of our streams in Fortnite and I think that will clarify a lot.

Development Streams

UBT runs the target rules file for editor builds in which devs work, as well as for each of the build targets (server/client x platform) when making builds. The editor build is also used for cooking, so whatever plugins are enabled in the editor builds is what’s available at cook-time. As explained in the article I shared, Fortnite implements its own ‘FortReleaseVersion’ naming system which can be a number like “30.00”, “30.10” or the special string “Future”.

  • We want our developers to have access to all assets, so UBT for the editor build enables all GFPs regardless of minimum or sunset FortReleaseVersion specified in the uplugin file.
  • When it’s time to make a build for development purposes, we made the decision to enable and cook all GFPs within a FortReleaseVersion range. For example, we’ll cook all GFPs in the version range (32.00 - 35.00) even if the next release will be 34.30. For that build target, UBT will only enable GFPs in that range. At cook-time our override of UGameFeaturesProjectPolicies::InitGameFeatureManager() will also load GFPs (amortized) in that FortReleaseVersion range by calling into UGameFeaturesSubsystem::Get().LoadBuiltInGameFeaturePlugins_Amortized().
  • At runtime, UGameFeaturesProjectPolicies::InitGameFeatureManager() will only load the GFPs for a specific FortReleaseVersion based on what version the developer is testing for. As a bonus, we implemented a way for devs to switch release versions in-game in a specific screen which will unload/reload GFPs.

We found this approach very useful for dev purposes: convenient editor experience and being able to test content for multiple release versions with the same build (though with the same code modules). This is how we do things on our development P4 streams.

Release Streams

Now on release streams (FortReleaseVersion XX.YY) things are much different and simpler. We want anyone working on a release stream to test as closely as possible to what players will experience and the build size to be representative, so UBT only enables the GFPs relevant to version XX.YY. In the editor only those GFPs are accessible. When cooking, only GFPs for version XX.YY are cooked and at run-time only those are loaded. Though in this case, just the UBT step would have been sufficient.

So to get back to this question: “is it mainly to control which GFPs are runtime loaded among the ones that have been packaged?”

Yes, but the runtime filtering only applies to development builds. For any shipped builds UBT has already left all GFPs for other versions disabled.

I hope that answers all your questions! If anything remains unclear please let us know.

Thank you Zhi!

That does indeed clarify all the details I was hoping to understand. Feel free to close the ticket. Thanks for digging in with me.

Cheers,

Daniele

Glad to hear that and happy to have investigated that for you!