-
Notifications
You must be signed in to change notification settings - Fork 0
Profile System
"Every subsystem in your game asks the same questions at startup: what platform am I on, how much memory do I get, should I enable this feature? If the answers live in twelve different places, you don't have configuration — you have archaeology."
Platform-aware configuration in Unreal Engine is a solved problem in theory. In practice, it is a distributed disaster.
Consider what happens when you ship on PC and Switch simultaneously. Your audio system needs to know the concurrent sound budget. Your streaming system needs memory pool sizes. Your PSO cache needs shader limits. Your save system needs to know whether external writes are permitted.
Where do those answers live today?
[DefaultEngine.ini] [PlatformA.ini] [C++ #if PLATFORM_SWITCH]
| | |
v v v
Audio reads Streaming reads Save system reads
from here from here from here
| | |
v v v
"Max sounds: 128" "Pool: 512 MB" "No external writes"
Three different mechanisms. Three different file formats. Three different moments in the initialization timeline. No way to query "what is the current truth" from a single API call. No way to simulate "what if we were on Switch" without rebuilding the project.
This pattern has three failure modes, all of which ship in real products:
Scattered authority. One team configures audio budgets in an ini file. Another team hardcodes streaming limits in a header. A third team uses preprocessor defines for save system behavior. Nobody knows the full picture. Nobody can audit the full picture.
Stale overrides. Platform-specific ini files diverge from the base configuration over months. A change to the base is not propagated. The Switch build silently runs with a budget that was correct six months ago.
No simulation path. You cannot answer "what would my game look like on Switch" without deploying to Switch hardware or maintaining a parallel configuration set. Profiling sessions on PC tell you nothing about platform constraints because the constraints are baked into platform-specific code paths.
The root cause is that Unreal Engine gives you the tools to configure per-platform behavior, but no single system that composes those configurations into a resolved truth that every subsystem can query. The engine gives you bricks. You need a building code.
PGX treats project configuration as a composed profile — a single resolved object that every subsystem in the framework can query through a uniform API.
The profile is not a settings file. It is a runtime-resolved document that combines five layers of configuration with platform overrides, applies deterministic merge rules, and exposes the result through a read-only query interface.
+-------------------------------------------------------+
| RESOLVED PROFILE |
| (read-only, queried by all 11+ subsystems) |
+-------------------------------------------------------+
^ ^ ^ ^ ^
| | | | |
+---------+ +-----------+ +--------+ +---------+ +----------+
| Identity| |Capabilities| |Policies| | Budgets | | Features |
+---------+ +-----------+ +--------+ +---------+ +----------+
Layer 1: Identity defines what the project IS. Project mode (game, simulation, tool, cinematic). Target platforms. Build context (development, testing, shipping). Restriction level (none, family, child). This layer is descriptive — it makes no decisions, it declares facts.
Layer 2: Capabilities defines what the project CAN DO. Ten boolean flags: persistent saves, external writes, persistent logs, hot reload, profiling, developer commands, analytics, virtual textures, ray tracing, nanite. These are not feature toggles — they are permissions. A capability set to false means no subsystem in the framework will attempt that operation, regardless of what individual system configurations say.
Layer 3: Policies defines HOW things operate. Persistence routing (local, cloud, custom). Security settings (encryption, signing, compression). These are behavioral directives that subsystems consume to select their operational mode.
Layer 4: Budgets defines resource ceilings. Nine numeric limits: maximum concurrent sounds, maximum save slots, shader cache budget, PSO pipeline budget, streaming pool in megabytes, RAM budget, VRAM budget, disk budget, and log disk budget. A value of zero means unlimited. These are hard caps that subsystems enforce — not suggestions.
Layer 5: Features defines rendering feature availability. Seven features (ray tracing, nanite, virtual textures, lumen, virtual shadow maps, world partition, hardware-accelerated features), each with a policy (allowed, fallback required, disallowed) and a flag indicating whether changing the policy requires an editor restart.
The base five-layer model describes the project's defaults. Platform Config assets describe per-platform overrides. Each platform config contains budget structs for every subsystem that enforces budgets.
Base Profile DA
+-------------------+
| Identity | Platform Config: Switch
| Capabilities | +-------------------------+
| Policies | | Audio Budget: 32 sounds |
| Budgets: 128 snd | | Save Budget: 8 slots |
| Features | | PSO Budget: 500 shaders |
+-------------------+ | Streaming: 256 MB |
| +-------------------------+
| |
v v
+---------------------------------------------+
| RESOLUTION ENGINE |
| Capabilities: AND (both must agree) |
| Budgets: MIN (most restrictive wins) |
| Features: RESTRICT (disallowed > fallback) |
+---------------------------------------------+
|
v
+---------------------------------------------+
| RESOLVED PROFILE |
| Audio: 32 sounds (Switch override won) |
| Save: 8 slots (Switch override won) |
| Ray Tracing: Disallowed (Switch said no) |
+---------------------------------------------+
The resolution rules are deterministic and conservative:
- Capabilities: AND merge. If the base allows saves and the platform allows saves, the result allows saves. If either says no, the result says no.
- Budgets: MIN merge (non-zero wins). If the base says 128 sounds and the platform says 32, the result is 32. If one says 0 (unlimited) and the other says 32, the result is 32.
- Features: Most Restrictive merge. Disallowed beats Fallback Required beats Allowed.
These rules guarantee that the resolved profile is always the most conservative interpretation of all inputs. No subsystem will ever exceed what any configuration layer permits.
When a project targets multiple platforms simultaneously (common during development), the profile resolves ALL target platform configs and applies the same merge rules across them.
Target: PC + Switch + Mobile
PC Config Switch Config Mobile Config
128 sounds 32 sounds 16 sounds
RT: Allowed RT: Disallowed RT: Disallowed
2048 MB stream 256 MB stream 128 MB stream
| | |
+------------------+-------------------+
|
MIN / AND / RESTRICT
|
v
Resolved: 16 sounds
RT: Disallowed
Stream: 128 MB
The result is the intersection of all platforms. Developing against this profile means your game works on your weakest target by construction.
In shipping builds, the profile is locked at resolution time. No runtime code can modify it. Simulation APIs are stripped. The resolved profile becomes a compile-time constant for all practical purposes.
In editor and development builds, the full simulation API is available. You can override the active platform, build context, or individual budget values to test behavior under different constraints without modifying any assets.
+---------------------------+
| Profile Subsystem |
| (GameInstanceSubsystem) |
| |
| Init: AssetRegistry scan |
| -> Discover config DA |
| -> Discover platform DAs |
| -> Resolve composed |
| -> Notify subscribers |
| -> Lock if Shipping |
+-------------+--------------+
|
+-----------------+-----------------+
| |
+-------v--------+ +--------v-------+
| Config DA | | Platform DA(s) |
| | | |
| 5-layer base | | Per-system |
| + platform | | budget structs |
| overrides | | + trait flags |
| + build | | |
| overrides | | One per target |
+----------------+ | platform |
+-----------------+
|
+-----------------+-----------------+
| | |
+-------v---+ +-------v---+ +--------v--+
| Resolution| | Simulation| | Delegates |
| Engine | | (Editor) | | |
| | | | | Resolved |
| AND/MIN/ | | Override | | Changed |
| RESTRICT | | platform | | PreChange |
| | | Override | | |
+-----------+ | build ctx | +------------+
+-----------+
|
+-------------------------+-----------------------+
| | | | |
+-----v----+ +-----v---+ +---v-----+ +---v----+ +---v------+
| Audio | | Save | | PSO | | Stream | | Loading |
| Budget: | | Budget: | | Budget: | | Budget:| | Budget: |
| sounds | | slots | | shaders | | MB | | RAM/VRAM |
+----------+ +---------+ +---------+ +--------+ +----------+
Engine Start
|
v
Profile.Initialize() <-- FIRST subsystem to init
|
+-- AssetRegistry scan for config DA
+-- AssetRegistry scan for platform DAs
+-- Resolve base layers
+-- Apply platform overrides (AND/MIN/RESTRICT)
+-- Apply build overrides
+-- Broadcast OnProfileResolved
+-- If Shipping: lock
|
v
GameFlow.Initialize() <-- Can query profile
|
v
Log.Initialize() <-- Can query profile
|
v
Save.Initialize() <-- Enforces save budgets from profile
|
v
PSO.Initialize() <-- Enforces shader budgets from profile
|
v
... (remaining subsystems) <-- All consume resolved profile
Profile initializes first by design. Every subsequent subsystem in the initialization chain can query the resolved profile during its own initialization. There is never a moment where a subsystem needs profile data and it is not yet available.
| Layer | Content | Example |
|---|---|---|
| Identity | Project mode, target platforms, build context, restriction level | "This is a Game targeting PC+Switch in Development mode" |
| Capabilities | 10 boolean permissions | "Allow saves, allow profiling, disallow external writes" |
| Policies | Persistence backend, security settings | "Use local saves with compression and checksums" |
| Budgets | 9 numeric resource limits | "128 max sounds, 16 save slots, 2048 MB streaming pool" |
| Features | 7 render features with policy | "Ray tracing: Allowed, Nanite: Fallback Required" |
| Data Type | Merge Strategy | Rationale |
|---|---|---|
| Booleans (Capabilities) | AND | Both sides must agree to enable |
| Integers (Budgets) | MIN (non-zero) | Most restrictive limit wins |
| Enums (Features) | Most Restrictive | Disallowed > Fallback > Allowed |
Eleven subsystems read their budget constraints from the resolved profile and enforce them as hard caps:
| Subsystem | Budget Enforced |
|---|---|
| Audio | Maximum concurrent sounds, channel count |
| Save | Maximum save slots, disk budget |
| PSO | Pipeline cache budget, shader limit |
| GameFlow | Channel history depth |
| Loading | RAM and VRAM allocation |
| LevelFlow | Streaming pool size |
| Log | Disk budget for persistent logs |
| MGOS | Memory pool monitoring thresholds |
| Message | Broadcast queue depth |
| EventHandler | Execution history limit |
| Data Registry | Cache size budget |
Without modifying any asset, you can test:
- "What if we were on Switch?" (override target platform)
- "What if this were a Shipping build?" (override build context)
- "What if we only had 256 MB streaming?" (override individual budget)
Simulation overrides apply on top of the resolved profile and broadcast change delegates, so every subsystem reacts in real time. Clear the simulation to return to the real resolved profile instantly.
Configuration follows the framework's data-driven philosophy. The developer creates a Data Asset in the Content Browser, fills in properties, and the system auto-discovers it at initialization.
1. Content Browser > Right-click > PGX > Profile
-> Creates a Project Profile Config asset
2. Fill in the five layers in the Details panel
-> Identity: set project mode, targets
-> Capabilities: toggle boolean flags
-> Policies: select persistence backend
-> Budgets: set numeric limits (0 = unlimited)
-> Features: set per-feature policy
3. (Optional) Create Platform Config assets
-> One per target platform
-> Set per-system budget overrides
4. Done. Profile auto-discovers via AssetRegistry.
No registration. No initialization code. No setup.
The Project Profile Config asset contains:
- Base layers: The five-layer model (Identity, Capabilities, Policies, Budgets, Features)
- Platform overrides: Per-platform adjustments applied after base resolution
- Build overrides: Per-build-context adjustments (development vs testing vs shipping)
Platform Config assets (v2.0) are separate Data Assets, one per platform, containing budget structures for each enforcing subsystem. They are discovered independently via the Asset Registry.
If no Profile Config asset exists in the project, the system operates with permissive defaults: all capabilities enabled, all budgets unlimited, all features allowed. This means the Profile system is backward compatible — adding it to an existing project changes nothing until you create and populate a config asset.
A Developer Settings entry under Project Settings provides:
- Default config asset reference (override for auto-discovery)
- Simulation defaults for editor testing
- Debug verbosity for profile resolution logging
The Profile Inspector is a dedicated editor tab with six visualization panels:
+----------------------------------------------------------+
| PGX Profile Inspector [Pin] |
+----------------------------------------------------------+
| |
| [Identity] [Capabilities] [Policies] |
| |
| Project Mode: Game |
| Targets: PC, Switch |
| Build: Development |
| Restriction: None |
| |
+----------------------------------------------------------+
| |
| [Budgets] [Features] [Platform Health] |
| |
| Sounds 128 ============================== [Override] |
| Slots 16 ======== [Override] |
| Shaders 2048 ============================== [Override] |
| Stream 512 =================== [Override] |
| |
+----------------------------------------------------------+
| Simulation: [Platform v] [Build v] [Clear] [Status] |
+----------------------------------------------------------+
Panel 1 — Identity: Project mode, active targets, build context, restriction level. Read-only display of resolved values.
Panel 2 — Capabilities: Ten boolean flags displayed as toggle indicators. Green for enabled, red for disabled. Shows the resolved value after AND merge.
Panel 3 — Policies: Persistence backend, security flags, compression settings. Displays the active policy configuration.
Panel 4 — Budgets: Nine resource limits with progress bars showing usage against budget. Per-subsystem breakdown.
Panel 5 — Features: Seven render features with color-coded policy status. Restart indicators for features that require editor restart to change.
Panel 6 — Platform Health Dashboard (v2.0): Aggregated view of all platform budgets across all target platforms. Shows which subsystems are within budget, which are approaching limits, and which would exceed limits on specific platforms.
The bottom bar of the inspector provides simulation controls:
- Platform dropdown: Select any target platform to simulate
- Build context dropdown: Select development/testing/shipping
- Clear button: Remove all simulation overrides
- Status indicator: Shows whether simulation is active and what overrides are applied
Simulation changes are instant. Every subsystem that listens to profile change delegates updates immediately.
The Profile Inspector is available from:
- PGX Toolbar > Tools submenu
- PGX Toolbar > Quick Access pin
- PGX Hub dashboard card
- System Observer subsystem list
Any subsystem that needs platform-aware behavior queries the Profile at initialization:
- During your subsystem's initialization, query the resolved profile for your relevant budgets and capabilities
- Subscribe to the change delegate if you need to react to runtime profile changes (editor simulation)
- Enforce budget limits as hard caps in your subsystem's operation
The Profile system is transparent to game code. Game developers interact with it in two scenarios:
- Initial setup: Create the Profile Config and Platform Config assets, fill in values
- Platform testing: Use the simulation controls to verify behavior under different platform constraints
No game code needs to reference the Profile directly unless implementing custom budget enforcement beyond what the framework provides.
The Profile broadcasts three delegate events:
| Event | When | Use Case |
|---|---|---|
| Profile Resolved | First resolution at startup | Subsystem initialization |
| Profile Changed | Any change (including simulation) | Live reaction to config changes |
| Profile Pre-Change | Before a change is applied | Validation, cleanup |
An optional interface allows objects to declare themselves as profile-aware. Objects implementing this interface receive direct notification of profile changes without manual delegate subscription.
The Profile system addresses a fundamental architectural problem: the gap between "the engine lets you configure per-platform behavior" and "your entire project actually behaves correctly on every platform."
Without a composed profile, every subsystem implements its own version of platform-aware configuration. Some use ini files. Some use defines. Some use runtime queries. The result is a project where no single person can answer "what are our resource limits on Switch?" without auditing a dozen files across a dozen systems.
With a composed profile:
- One asset defines all platform budgets. One place to audit. One place to change.
- One API answers all queries. No per-system configuration archaeology.
- One simulation tests all platforms. No rebuilds, no device farms, no guesswork.
- Deterministic resolution means the most conservative interpretation always wins. If something works in simulation, it works on hardware.
The Profile initializes first in the framework's boot sequence. By the time any subsystem asks "what is my budget?", the answer is already resolved, cached, and available. The question of authority — who decides what a subsystem is allowed to do — is answered once, at startup, and never again until the developer explicitly changes the profile.
This is the difference between a project that happens to work on multiple platforms and a project that is designed to work on multiple platforms from the first line of configuration.
| Command | Description |
|---|---|
pgx.profile.status |
Print resolved profile summary (state, platform, build) |
pgx.profile.dump |
Dump all five layers with current resolved values |
pgx.profile.capability <name> |
Query a specific capability by name |
pgx.profile.budget <name> |
Query a specific budget by name |
pgx.profile.feature <name> |
Query a specific render feature by name |
pgx.profile.simulate.platform <name> |
Simulate a target platform (editor only) |
pgx.profile.simulate.build <context> |
Simulate a build context (editor only) |
pgx.profile.simulate.clear |
Clear all simulation overrides (editor only) |
pgx.profile.simulate.status |
Display current simulation override status |
Organized into three categories:
| Category | Nodes |
|---|---|
| Core | Get Resolved Profile, Is Capability Enabled, Get Budget |
| Query | Is Feature Allowed, Get Profile State, Is Profile Resolved, Get Persistence Backend |
| Advanced | Simulate Platform, Simulate Build Context, Clear Simulation, Has Simulation Overrides, Get Active Targets |
- Development Preview
- Getting Started
- Release branch catalog
- Public Plugin Matrix
- Early Preview Plugins
- Known Issues
- Architecture Overview
- Plugin Topology
- Module Reference
- Configuration and Registry
- Data-Driven Design
- Profiles and Budgets
- Gameplay Tag Architecture
- Initialization Pipeline
- Cross-Plugin Communication
- Message System
- Event Handlers
- Logging and Trace
- Runtime Flows
- Blueprint API Design
- Editor Integration
- Editor Visual System