Skip to content

Loading and ticking

Designers and programmers · 10 min read · No code

Who this is for: anyone adding content who wants it to stay cheap. The first half (which references to make soft, and when to tick) is for designers working in Blueprints and Details panels; the warning boxes marked For programmers are for whoever writes C++.

Two things decide most of what XenoCore costs at run time: what an asset drags into memory when it loads, and what runs every frame. A property that points straight at another asset (a hard reference) loads that asset, and everything it points at, whenever its owner loads. A Blueprint with a live Event Tick runs a Blueprint call every frame for every copy in the world. The idea that makes the rest make sense: load an asset when it is needed, not when something that might need it loads; and run code when something changes, not every frame in case it did.

Hard and soft references

A soft reference stores the asset's path only. In the Details panel it looks and works exactly like a hard one: the same picker, the same thumbnail. The difference is that nothing loads until code asks for it.

Hard is right when the owner cannot do its job without the asset the moment it is used:

  • a character's meshes, anim Blueprint and the montages it always plays;
  • its Pawn Data and ability sets;
  • the widget classes a screen builds on open.

Soft is right when the asset is optional, situational, one of many, or large and needed later:

  • drop pools, shop and trade stock, quest targets, learnable Arts, rare effects;
  • a default that a Blueprint child is expected to override (fonts, glyph widgets, textures).

These XenoCore properties are soft. Existing data converted on its own; you set them the same way as before.

Property Where Loads when
Enemy Target Quest objective Never by the quest. Kills are matched against enemies already in the world. See Quests
Art Art learnset rows (DT_XenoCore_ArtLearnset_*) The character learns it: at spawn or level-up. See Arts
Item Def Drop table entries A chest is opened, and only the items rolled. See Loot
Item (offers, overtrade bonus) Shop and trade tables The shop or trade screen opens. See Shops and trade
Death Effect Enemy The enemy is first provoked, so it is ready by the time the body vanishes. See Enemies and AI
Glyph Class, Action Table, Font Family Tactics panel (XenoCoreTacticsPanelBase) The panel is first built. See Party tactics
Font Family, Sun Texture, Moon Texture Change Time screen (XenoCoreChangeTimeScreen) The screen is first built. See Time of day and weather

Blueprint graphs that read these fields

A graph reading one of the properties above now gets a soft reference. Load it with Load Class Asset Blocking / Load Asset Blocking at a pause (below), or Async Load Class Asset / Async Load Asset if it can wait. (For programmers: in C++, the shop and trade rows have LoadItem().)

Where to load a soft reference

  • At a natural pause: a menu opening, a chest opening, a level-up, the moment an enemy is provoked. The player expects a beat there, and one load is invisible.
  • Never per frame or per hit. Loading from a tick or a damage path is a path lookup every call at best, and a disk read at worst.
  • Cache what you load. Keep the loaded pointer (or the async handle) while it is in use, so the second call is free and garbage collection does not unload it mid-fight.

The XenoCore Content and XenoCore UI project settings already work this way: each setting is resolved the first time it is read and then kept. Editing a setting clears the cache.

Fails silently in the editor: caching an asset on a settings object

For programmers. In a cooked game, settings objects created at startup sit in a memory pool the garbage collector never walks, so an asset cached on one is not kept alive by it. The editor never shows this. A Development build halts on the title screen ("breaking Disregard for GC assumptions"); a Shipping build frees the asset under the pointer. XenoCore's own caches go through XenoCoreSettingsCache::KeepAlive; do the same in any settings class you add, and test a packaged build.

Ticking

  • Prefer events and timers. React to a gameplay message, a delegate or a timer. Most "is it time yet?" checks run perfectly well every 0.2 to 0.5 s.
  • Throttle what must poll. XenoCore's own polls run on intervals: the enemy's label decides whether to tick every Label Tick Check Interval (0.4 s), and the environment director refreshes brightness, fog and sky colour at most every 0.1 s.
  • Turn tick off when idle. A widget or component with nothing to do should stop ticking and start again when something changes. Enemy health bars and labels do this.
  • No per-frame searches. Get Component by Class, Get All Actors of Class and similar iterators belong in Begin Play or behind a timer, with the result cached.

Fails silently: an Event Tick that only calls its Parent

A Blueprint whose Event Tick (or Event Blueprint Update Animation) is wired only to Parent: Tick does nothing extra, but it still makes every copy run a Blueprint call each frame. Nothing warns. Delete the node; the parent's tick still runs without it. A greyed-out Event Tick placed by default and left unconnected costs nothing.

Measuring

What an asset drags in. Right-click an asset in the Content Browser and choose Size Map: it shows everything the asset loads with it, by size. Reference Viewer (same menu) shows which property holds each reference. Compare a whole play session rather than single assets: one asset's cost overstates the saving when the map or the party loads the same content anyway.

Which Blueprints tick. Open a Blueprint's Event Graph and look for a connected Event Tick; see the warning above.

For real numbers, in Play In Editor or a packaged build, open the console:

Command Shows
stat game Time spent in game-thread tick, per category
stat unit Frame, game, draw and GPU time
dumpticks Every registered tick function, enabled or not
memreport -full Memory by class and asset, written to Saved/Profiling/MemReports

Unreal Insights (run with -trace=default) shows which ticks and loads cost what, frame by frame.

Looked at and kept

These came up and were deliberately left alone, so there is no need to look again:

Candidate Why it stays
Body Meshes on the party base classes The template pawn already loads both Mannequin bodies, so a soft reference saves nothing
Member Class on party member definitions Every starting member spawns at New Game, and the party and progression screens read benched members' class defaults
Lyra's fonts Already set to Lazy Load
Gaur Plain's map size The fix is converting the map to World Partition, not trimming references. See Build a world-partition map