# How Unciv Handles Transient References During Game State Serialization

> Discover how Unciv's game state serialization manages transient references using @Transient and setTransients() to ensure data integrity and prevent concurrency issues during saves.

- Repository: [Yair Morgenstern/Unciv](https://github.com/yairm210/Unciv)
- Tags: internals
- Published: 2026-06-18

---

**Unciv marks runtime-only object references with Kotlin's `@Transient` annotation to exclude them from JSON serialization, clones the game state before saving to avoid concurrency issues, and rebuilds the complete object graph via `setTransients()` methods after deserialization.**

Unciv, the open-source Android reimplementation of Civilization V, persists game states using libGDX's JSON serializer. Because the live game graph contains circular references, cached calculations, and volatile runtime data that cannot be reliably stored, the codebase implements a coordinated three-phase strategy to handle transient references during serialization.

## The Three-Phase Serialization Strategy

### Phase 1: Marking Fields as Transient

Any property that should not be persisted—including object back-references and derived caches—is annotated with Kotlin's `@Transient`. This instructs the libGDX JSON serializer to skip the field when writing to disk, preventing serialization of stale or circular data.

In [`core/src/com/unciv/logic/map/tile/Tile.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/map/tile/Tile.kt), the `roadOwnerObject` property demonstrates this pattern:

```kotlin
class Tile {
    @Transient private var roadOwnerObject: Civilization? = null
    // This reference is rebuilt after loading, not stored in JSON
}

```

### Phase 2: Cloning Before Serialization

To prevent concurrent modification errors during the save process, Unciv creates a deep clone of the entire `GameInfo` tree before serialization. According to the implementation in [`core/src/com/unciv/logic/files/UncivFiles.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/files/UncivFiles.kt), this ensures the serializer operates on a stable snapshot while the live game continues running.

```kotlin
val clonedGame = gameInfo.clone()
val jsonString = Json().toJson(clonedGame)

```

### Phase 3: Restoring Transient References

After JSON is read back into objects, Unciv calls `setTransients()` to rebuild the object graph. This method traverses the loaded data and re-establishes all the references that were excluded from the JSON payload.

- **`GameInfo.setTransients()`** orchestrates the restoration process by invoking setTransients on child objects.
- **`Civilization.setTransients()`** recreates internal caches and unit lists.
- **`Tile.setTransients()`** restores owner references like `roadOwnerObject`.

The comment in [`core/src/com/unciv/logic/civilization/managers/UnitManager.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/civilization/managers/UnitManager.kt) explains this regeneration: "Used during load game via `setTransients` to regenerate a Civilization's list from the serialized Tile fields."

## Critical Implementation Details

### Custom Serialization Rules

Not all types serialize automatically with libGDX's default rules. The file [`core/src/com/unciv/logic/multiplayer/apiv2/JsonSerializers.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/multiplayer/apiv2/JsonSerializers.kt) provides custom serializers for types like `Instant` and `UUID` that require special handling during game state serialization.

### Enum and Lazy Property Handling

As documented in [`core/src/com/unciv/logic/GameInfo.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/GameInfo.kt), enums serialize as their primitive values, and extra enum fields are ignored during deserialization. The same file warns that properties declared with `by lazy` should explicitly use `@Transient` for safety, a guideline reinforced in [`core/src/com/unciv/ui/screens/civilopediascreen/FormattedLine.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/ui/screens/civilopediascreen/FormattedLine.kt) regarding fields without backing fields.

## Summary

- **Mark with `@Transient`:** Exclude runtime references and cached data from JSON output using Kotlin's transient annotation, as seen in [`Tile.kt`](https://github.com/yairm210/Unciv/blob/main/Tile.kt).
- **Clone before saving:** Create a deep copy of `GameInfo` in [`UncivFiles.kt`](https://github.com/yairm210/Unciv/blob/main/UncivFiles.kt) to avoid race conditions during serialization.
- **Rebuild after loading:** Call `setTransients()` on `GameInfo`, `Civilization`, and `Tile` objects to restore the object graph and cached collections.
- **Handle edge cases:** Use custom serializers in [`JsonSerializers.kt`](https://github.com/yairm210/Unciv/blob/main/JsonSerializers.kt) for complex types and explicitly annotate lazy properties as documented in [`GameInfo.kt`](https://github.com/yairm210/Unciv/blob/main/GameInfo.kt).

## Frequently Asked Questions

### Why does Unciv clone the game state before serializing?

The clone operation in [`UncivFiles.kt`](https://github.com/yairm210/Unciv/blob/main/UncivFiles.kt) prevents concurrent modification exceptions. If the live game changed while the serializer was reading the object graph, the resulting JSON would be corrupted. Operating on a clone ensures a consistent snapshot while the original game state continues running.

### What happens if a developer forgets to mark a field as @Transient?

Unmarked reference fields would be serialized as nested objects, potentially creating duplicate data or circular reference errors. Upon deserialization, these fields might contain stale object copies rather than references to the actual live objects, causing subtle gameplay bugs or memory leaks.

### How does setTransients() handle circular references between civilizations and tiles?

The `setTransients()` method in `GameInfo` establishes a deterministic restoration order. It first sets up the top-level game components, then passes references downward to civilizations and tiles, allowing each entity to link back to its owners without creating infinite loops during the rebuild process.

### Are lazy properties automatically excluded from serialization?

While Kotlin's `by lazy` delegates are typically not serialized, the codebase explicitly marks them with `@Transient` as a safety measure. The [`GameInfo.kt`](https://github.com/yairm210/Unciv/blob/main/GameInfo.kt) file documents this requirement, noting that explicit annotation prevents accidental serialization of backing fields that should be recalculated at runtime.