What Are Transient Caches in Unciv and How Do They Optimize Performance?

Transient caches in Unciv are runtime-only data structures rebuilt after loading a game via setTransients() methods, eliminating expensive recalculations while keeping save files small by excluding cached data from JSON serialization.

Unciv, the open-source Android implementation of Civilization V hosted at yairm210/Unciv, relies heavily on transient caches to maintain fluid gameplay on low-end hardware. These caches store computed values—such as civilization resources, visible tiles, and parsed rules—that would be prohibitively expensive to recalculate every frame, yet they never appear in saved games because they are marked with Kotlin's @Transient annotation.

How Transient Caches Work in Unciv

Unciv stores most game objects—including tiles, units, cities, and rulesets—as plain data that can be serialized to JSON for saving and loading. Fields marked with @Transient are not written to the save file. Instead, after a game loads or a new game starts, a dedicated rehydration process invokes setTransients() methods across the object graph to rebuild these runtime-only caches.

This architecture delivers two critical optimizations: it prevents large save files by storing only minimal identifiers, and it guarantees that heavy derived data is recomputed only when necessary rather than during every game loop iteration.

The Freeze-Dry Rehydration Pattern

The transient cache system follows a strict four-step lifecycle:

  1. Save – Only essential identifiers (names, IDs, coordinates) are written to JSON; @Transient fields are automatically skipped by the serializer.
  2. Load – GameInfo.setTransients() traverses the loaded object tree and initializes cache objects for civilizations, units, and the map.
  3. Runtime – Game logic reads directly from cached collections, which provide O(1) lookups instead of O(N) scans across the entire game state.
  4. Invalidate – When underlying data changes—such as after a unit moves, a building completes, or a turn ends—the relevant cache's setTransients() or a specific update method (e.g., updateCivResources()) refreshes only the affected data.

Key Transient Cache Implementations

CivInfoTransientCache

Located in core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt, this cache holds civilization-wide computed data including unique units and buildings, resource supply totals, viewable tiles, and city-capital connections. It is instantiated by GameInfo.setTransients() and CivConstructions.setTransients(). By storing detailedCivResources and civResourcesUniqueMap as pre-computed maps, the cache eliminates per-city resource aggregation on every UI update.

MapUnitCache and Pathfinding

The MapUnitCache class in core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt maintains per-unit transient state including references to the current tile and movement characteristics. For pathfinding, Unciv uses a PathfindingCache (defined within UnitMovement.kt) that reuses node graphs across multiple path queries, preventing repeated allocation of expensive pathing objects. The PathingMapCache in core/src/com/unciv/logic/map/PathingMapCache.kt further optimizes this by caching pre-computed neighbor information for the entire tile map.

RulesetCache

Found in core/src/com/unciv/models/ruleset/RulesetCache.kt, this global cache stores fully loaded ruleset objects combining base rules and active mods. Because mod loading involves heavy parsing and validation, the RulesetCache guarantees each unique ruleset combination is parsed only once and shared across game sessions via loadRulesets().

LocalUniqueCache

Located in core/src/com/unciv/models/ruleset/unique/LocalUniqueCache.kt, this cache stores parsed Unique objects indexed by their source (e.g., a specific building or unit). Since parsing unique text strings is computationally costly, caching the parsed sequence allows fast reuse during game logic evaluation.

Performance Optimization Strategies

Transient caches optimize Unciv through three specific mechanisms:

  • Avoiding repeated expensive lookups – Instead of scanning every tile to calculate visible territory or iterating all cities to sum resources every frame, the game reads from pre-computed maps that update only when the underlying state mutates.
  • Minimizing save file size – By excluding heavy derived data from JSON serialization, save files remain small enough for quick cloud syncs and low-storage mobile devices.
  • Ensuring cache consistency – Because caches are never persisted, they cannot become stale across game sessions. They are rebuilt fresh on every load, ensuring deterministic behavior regardless of previous game states.

Implementation Examples

Rehydrating Civilization Caches After Loading

When a saved game loads, the rehydration process rebuilds all transient state:

// When a saved game is loaded:
val game = UncivGame()                       // creates a fresh GameInfo
if (RulesetCache.isEmpty())                 // ensure rulesets are loaded first
    RulesetCache.loadRulesets(noMods = true)

game.gameInfo.setTransients()                // <-- calls setTransients on all sub‑objects

Source: GameInfo.setTransients() calls CivInfoTransientCache.setTransients() – see core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt.

Accessing Cached Civilization Resources

// Inside any civ‑related logic:
val civResources = civInfo.civResourcesUniqueMap   // already cached
// No need to iterate over every city each frame; the cache is refreshed only when resources change.

Source: CivInfoTransientCache.updateCivResources() builds detailedCivResources and civResourcesUniqueMap.

Reusing Pathfinding Data

val pathCache = unit.pathfindingCache               // created once per unit
val shortestPath = pathCache.findPath(start, goal) // reuses the cached node graph

Source: PathfindingCache inside UnitMovement.kt.

Loading Rulesets Once Per Session

// At application start:
if (RulesetCache.isEmpty())
    RulesetCache.loadRulesets(consoleMode = false, noMods = false)

Source: RulesetCache.loadRulesets() in core/src/com/unciv/models/ruleset/RulesetCache.kt.

Summary

  • Transient caches are @Transient fields rebuilt via setTransients() after loading, never saved to JSON.
  • CivInfoTransientCache stores civilization-wide data like resources and visible tiles to eliminate per-turn scans.
  • MapUnitCache and PathingMapCache optimize unit movement and pathfinding by reusing node graphs.
  • RulesetCache and LocalUniqueCache prevent repeated parsing of heavy mod and unique data.
  • The freeze-dry pattern keeps save files minimal while ensuring O(1) access to computed data during gameplay.

Frequently Asked Questions

What does the @Transient annotation do in Unciv?

The @Transient annotation marks Kotlin fields that should be excluded from JSON serialization. In Unciv, this means the data is not written to save files and must be reconstructed when the game loads through the setTransients() rehydration process.

When are transient caches rebuilt during gameplay?

Transient caches are rebuilt immediately after a game finishes loading via GameInfo.setTransients(), and they are incrementally updated when specific state changes occur—such as calling updateCivResources() when a city completes a building or a trade deal modifies resource counts.

How do transient caches affect save file size?

Transient caches dramatically reduce save file size because they store only essential identifiers (like civilization names or unit IDs) in JSON, while omitting heavy computed data like pathfinding graphs or parsed ruleset objects. This keeps saves small enough for efficient storage and cloud synchronization on mobile devices.

Which cache handles pathfinding optimization for individual units?

The MapUnitCache in core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt handles per-unit pathfinding optimization by maintaining a PathfindingCache that reuses node graphs across multiple movement queries, preventing the repeated allocation of expensive pathing data structures.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →