top of page
Search

🧠 Becoming the God of Memory: How I Use Async Loading to Control Gangs of Gaddis World in Unreal Engine

As a game developer, I often think of myself as the god of my world — deciding what exists, when it exists, and how long it stays in memory.

While artists and designers create the world’s beauty — the reflections, the smoke trails, the intricate metal panels on a car — I’m the one making sure that beauty runs at 60 frames per second.

In my game Gangs of Gaddis, which mixes vehicle combat, explosions, and powerup chaos inside colorful Indian-inspired towns, every second on screen involves hundreds of assets competing for memory.

So my real challenge isn’t about visuals — it’s about control. Control over what’s loaded in RAM, what’s cached, and what can be silently released back to the void.

That’s where Unreal Engine’s Async Loading System becomes my divine instrument.


Fieol working on Gangs of Gaddis
Fieol working on Gangs of Gaddis



🎮 The Problem: Uncontrolled Memory Chaos

Every game world feels infinite to the player, but to a low-end device, it’s a battlefield for survival.

A high-end PC or console can load gigabytes of textures, meshes, and effects without breaking a sweat. But on a low-end Android device, the scene changes completely — suddenly, you’re working inside a 250 MB memory pool.

That 250 MB is not your full RAM — it’s just your slice of it.It’s where your entire game world must fit: vehicles, powerups, UI, explosions, materials, sounds, and the level itself.

Once your game crosses that boundary, Android begins to reclaim memory aggressively. You’ll start seeing:

  • Textures streaming late or popping in.

  • The game pausing for garbage collection mid-action.

  • Or worse — the app getting terminated silently by the OS.




🧱 The Unified Memory Challenge

Mobile GPUs like PowerVR GE8320, Mali-G52, or Adreno 506 don’t have dedicated VRAM. They share the same pool as the CPU, known as Unified Memory.

That means every polygon you render, every texture you sample, and every particle you spawn competes with your gameplay logic, audio, and physics for the same resource.

A 4K texture alone can eat 21 MB of memory uncompressed. Multiply that by five vehicles, a few decals, some VFX, and your GPU driver starts juggling memory — evicting old buffers, paging new ones in — all while trying to maintain 60 FPS.

The result? Micro-stutters, overheating, and frame drops.

Your explosions might look stunning, but they just killed your framerate.

That’s why you can’t leave memory to chance. When your world lives inside 250 MB, every byte becomes sacred. You can’t afford to preload every asset or keep meshes alive “just in case.”

That’s where I step in — not as an artist, but as the architect.

I make sure the world doesn’t just exist beautifully… it exists sustainably.




⚙️ The Async Loading System — My Control Lever

When you build for limited hardware, you quickly learn:

You don’t fight memory. You choreograph it.

That’s exactly what UE Async Loading System lets me do.

It’s not about making assets load faster — it’s about making them load intelligently.

Instead of freezing the game while an asset is being loaded, Async Loading streams it in the background, letting gameplay continue. It’s invisible to the player but monumental for performance.


🔄 How It Works

At the core lies Unreal’s StreamableManager, which handles asset loading asynchronously through a FStreamableHandle.

Here’s a simplified example of how I load a vehicle dynamically:


FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
TSharedPtr<FStreamableHandle> Handle = Streamable.RequestAsyncLoad(  VehicleAssetPath, FStreamableDelegate::CreateUObject(this, &UGOGAssetManager::OnVehicleLoaded));

This small piece of code gives me full control:

  1. The asset loads in the background — no frame hitches.

  2. I get a callback once it’s ready.

  3. I can spawn it into the level immediately.

And once that vehicle is no longer needed:

Handle->ReleaseHandle();

That’s it. Unreal’s garbage collector will reclaim it in the next cleanup cycle.

No leaks. No ghosts in memory. No unpredictable behavior.



💾 Why Async Loading Saves You on Low-End Devices

On PowerVR and Mali GPUs, every asset you keep loaded adds pressure on the unified memory pool.Async loading keeps that pool balanced.

When an explosion or a car spawns, Unreal allocates buffers and texture memory on the GPU.If too many assets exist at once, the driver will start swapping buffers between CPU and GPU memory — and that’s where frame hitches appear.

By loading assets only when needed and releasing them immediately after use, I prevent these spikes.

In my tests:

  • GPU memory usage dropped by 38% during vehicle combat.

  • Frame hitches reduced by 70% during heavy action scenes.

That’s not theoretical — it’s real-world performance reclaimed from chaos.



🧰 My Async Loading Best Practices

Here’s what I follow to make this system bulletproof across all platforms:

  1. Preload only what’s persistent. HUDs, game modes, and critical logic assets stay loaded. Everything else streams on demand.

  2. Load by context. Vehicles stream in when the player approaches a combat zone. Powerups stream only when needed in the area.

  3. Pool frequently reused assets. Explosions, decals, and sparks are pre-instantiated and recycled — not respawned repeatedly.

  4. Use priorities. Essential gameplay assets load with higher priority handles than decorative or ambient elements.

  5. Track and release. Every FStreamableHandle I request is released the moment its object’s lifecycle ends.

  6. Schedule Garbage Collection manually. I time lightweight GC passes after matches or transitions to avoid spikes mid-action.

  7. Profile every load with Unreal Insights. More on that later — but it’s the key to seeing how your system really performs.



🧩 Real-World Example: Vehicles, Powerups, and Explosions

Async loading shines when the world gets heavy. Here’s how it saves my game every day.


🚗 Vehicles — The Heavy Hitters

Even optimized, a single vehicle asset weighs around:

  • Mesh & Skeleton: 2.5 MB

  • Textures & Materials: 12–16 MB

  • Physics & Audio: 4 MB

  • Particles & Extras: 2 MB

That’s 25 MB per vehicle. Spawn ten, and you’ve burned through your mobile memory pool.

So instead, I load them asynchronously just before they’re needed.

  • When a player enters an arena zone, I start async loading the nearby vehicles.

  • By the time combat begins, they’re ready in memory.

After the match, I release the handles — freeing 20–30 MB per vehicle immediately.

On PowerVR-based phones, this optimization alone dropped VRAM use by 38%, and frame hitch durations fell from 0.4s to 0.1s.


⚡ Powerups — Small but Deadly

Powerups look innocent but are frequent. Each involves:

  • A mesh or skeletal object (1–2 MB)

  • Material & glow (1 MB)

  • FX particle (2–4 MB)

  • Audio (0.5 MB)

With 30 powerups in play, that’s 100 MB wasted if unmanaged.

So I maintain a Powerup Pool — a pre-instantiated list of reusable actors. They’re never destroyed — just toggled active/inactive. When a new type appears for the first time, it async loads in the background and joins the pool.

Result: 80% reduction in spawn overhead and consistent memory behavior.


💥 Explosions — The Hidden Memory Spike

Explosions are deceptive. Each one brings:

  • Particle systems (4–6 MB)

  • Animated materials (2 MB)

  • Sound cues & decals (1 MB)

During intense firefights, dozens may trigger at once, suddenly allocating 50–60 MB of transient GPU memory.

My fix? Lazy streaming with a tiny cache.

  • First explosion async loads its data (~100 ms delay, off the main thread).

  • The next few reuse cached versions.

  • After 10–15 seconds of inactivity, they’re released.

That one change stabilized my Mali-G52 GPU usage from 95–98% peaks to under 80%, eliminating visible frame drops during chain explosions.




📈 Async vs Sync Cost

Scenario

Synchronous Load (ms)

Async Load (ms)

Frame Impact

Vehicle Mesh + Textures

400–600

90–120

No hitch

Powerup Actor

150–250

50–70

Smooth

Explosion FX

300–450

100–130

Stable

Async doesn’t make loading free — it makes it invisible.



💾 Memory Before vs After


System

Before

After

Improvement

Vehicles

220 MB

120 MB

45%

Powerups

100 MB

55 MB

45%

Explosions

60 MB spikes

20 MB stable

66%



🧠 Using Unreal Engine Insights to Understand Memory Traces

All of this optimization would be guesswork if I couldn’t see it happening. That’s where Unreal Insights comes in — Unreal’s built-in profiler that lets me peek inside the engine’s mind.

I like to think of Insights as an X-ray of my game’s memory behavior.

When I record a trace, it shows me:

  • Load events for assets streamed asynchronously.

  • Memory allocation spikes and which categories caused them.

  • Garbage Collection cycles and object lifetimes.

  • CPU and GPU thread timings, revealing exactly when the main thread stalls.



🧭 My Profiling Workflow

  1. Enable tracing I launch the game with:

    -trace=cpu,loadtime,memory

    This ensures I capture all load and memory events.


  2. Record gameplay I play for 3–5 minutes, covering intense moments — vehicle collisions, explosions, powerup pickups.


  3. Open Unreal Insights I inspect the Memory Overview tab. Unreal breaks memory down into categories:

    • Textures

    • Static Mesh Buffers

    • Skeletal Meshes

    • Audio

    • Physics

    • Async Load Requests


  4. Correlate with gameplay I match spikes in memory or async loads to specific events — like a vehicle entering the scene or a wave of explosions firing off.

  5. Optimize accordingly

    • If I see a spike from texture streaming, I reduce mip levels.

    • If explosion VFX stay in memory too long, I shorten their cache duration.

    • If GC stalls are visible, I move cleanup to post-combat phases.

This turns optimization into a visible process.

🧩 Pro Tip: Always test in standalone mode, not the Editor. The Editor’s overhead can distort real runtime data.

📊 What Insights Revealed

One of my favorite sessions showed:

  • A 180 MB RAM spike exactly when 8 vehicles spawned together.

  • A slow-release pattern in explosions that kept memory bloated post-fight.

  • Powerup actors that weren’t being released because of lingering material instances.

After applying targeted fixes, I saw:

  • Memory flattening into predictable waves.

  • GC cycles running 40% faster.

  • 100% frame consistency during transitions.

That’s the power of visibility.You can’t optimize what you can’t measure — and Insights makes the invisible, visible.



🎨 Balancing Creative Vision and Performance

For an artist, beauty means density — more particles, more geometry, more visual richness.For me, beauty means balance — preserving that richness without breaking the player’s immersion.

There’s always a moment in development when creative vision meets technical reality.That’s where I step in — to translate the vision into something sustainable for the hardware.

Async loading lets me preserve art without sacrificing performance.

“The artist decides how the world should look.The developer ensures it can exist.”


📚 References and Resources

If you’d like to explore further, here are the core references that guided my system design:




🧩 Conclusion: The Developer’s Invisible Role

Players will never see async handles, streaming queues, or memory traces. But they’ll feel it — in every smooth drift, every explosion, every uninterrupted moment.

That’s the invisible art of being a developer.

It’s not about the code the player sees, but about the control that they never notice — the orchestration of loading, unloading, caching, and releasing that keeps the world alive.

As a developer, I may not paint the world. But I decide when the paint appears — and when it gracefully fades away.

That’s what it means to be the God of Memory —to make beauty sustainable, performance invisible, and control absolute.

 
 
 

Comments


bottom of page