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.