# Pyrite64 Asset Management and Automatic Memory Cleanup: Internal Strategies Explained

> Explore Pyrite64's internal asset management and automatic memory cleanup strategies. Learn how UUID indexing, shared_ptr, and file polling ensure efficient resource handling and reclamation.

- Repository: [Max Bebök/pyrite64](https://github.com/hailtododongo/pyrite64)
- Tags: internals
- Published: 2026-02-19

---

**Pyrite64 uses a centralized `AssetManager` class with UUID-based indexing, `std::shared_ptr` reference counting, and live file-system polling to handle textures, models, and scripts while automatically reclaiming memory when assets are removed or reloaded.**

The `AssetManager` in the Pyrite64 engine (found in `HailToDodongo/pyrite64`) implements a self-healing asset pipeline that eliminates manual memory management. By combining eager cataloging with lazy loading and reference-counted ownership, the system ensures GPU and heap resources are released immediately when no longer needed.

## Unified Asset Cataloging and UUID Indexing

At startup, `AssetManager::reload()` in [`src/project/assetManager.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/assetManager.cpp) recursively scans the **assets** folder to build a comprehensive catalog. Every file receives an `AssetManagerEntry` containing metadata and a unique identifier, categorized by `FileType` into a `std::array<std::vector<AssetManagerEntry>>` structure.

The system maintains two critical data structures for O(1) lookups:

- **`entries`**: A type-segregated array of vectors storing the actual asset entries
- **`entriesMap`**: An unordered map providing UUID-to-index translation for instant retrieval

```cpp
for (const auto &entry : fs::recursive_directory_iterator{assetPath}) {
    if (entry.is_regular_file()) {
        auto path = entry.path();
        watchFiles[path.string()] = Utils::FS::getFileAge(path);
        AssetManagerEntry assetEntry{};
        if (!buildAssetEntry(project, path, assetEntry)) continue;
        
        if (assetEntry.type == FileType::IMAGE && ctx.window) {
            reloadEntry(assetEntry, path.string());
        }
        if (assetEntry.type == FileType::PREFAB) {
            reloadEntry(assetEntry, path.string());
            if (assetEntry.prefab) assetEntry.conf.uuid = assetEntry.prefab->uuid.value;
        }
        entries[(int)assetEntry.type].push_back(assetEntry);
    }
}

```

*Implementation reference*: [`src/project/assetManager.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/assetManager.cpp) lines 80-104

## Reference-Counted Resource Ownership

Heavy resources including GPU textures, 3D meshes, and prefabs are wrapped in `std::shared_ptr` to enable automatic memory cleanup. The `reloadEntry()` method in [`src/project/assetManager.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/assetManager.cpp) is the sole location where GPU-bound objects are instantiated, ensuring centralized ownership tracking.

```cpp
case FileType::IMAGE:
    entry.texture = std::make_shared<Renderer::Texture>(ctx.gpu, path, isMono);
    break;
case FileType::PREFAB:
    entry.prefab = std::make_shared<Prefab>();
    entry.prefab->deserialize(Utils::FS::loadTextFile(path));
    break;

```

When `reload()` clears the type vectors using `for (auto &e : entries) e.clear();`, all `shared_ptr` references go out of scope. Their destructors automatically invoke the corresponding GPU memory or heap deallocators without explicit `delete` calls.

## Live File-System Watching and Incremental Reload

The `pollWatch()` method implements a differential file-system watcher that executes every 2 seconds (configurable via `kMinInterval`). It snapshots file timestamps, compares them against the previous state stored in `watchFiles`, and categorizes changes into **added**, **modified**, or **removed** assets.

```cpp
if (it == watchFiles.end()) {
    addedAssets.push_back(pathStr);
} else if (it->second != age) {
    modifiedAssets.push_back(pathStr);
}

```

*Implementation reference*: [`src/project/assetManager.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/assetManager.cpp) lines 71-87

When paths are detected as removed, entries are erased from both the `entries` vectors and `entriesMap`. Because these containers store `shared_ptr` objects, erasure immediately drops the final reference count, triggering destructor-based cleanup of the underlying resources. Modified assets trigger `reloadEntry()` to refresh the resource while maintaining the same UUID mapping.

## Practical Implementation Examples

### Querying Assets by UUID

Runtime code retrieves assets safely without manual memory management concerns using `getEntryByUUID()`:

```cpp
uint64_t uuid = 0x12345678abcdef00;
auto *entry = ctx.project->getAssets().getEntryByUUID(uuid);
if (entry && entry->type == Project::FileType::IMAGE) {
    auto tex = entry->texture;  // shared_ptr copy increments ref-count
    // Texture remains valid for this scope; automatic release follows
}

```

*Reference*: [`src/project/assetManager.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/assetManager.h) lines 33-39

### Forcing Full Asset Reloads

For bulk file-system changes or manual refresh triggers, the reload cycle can be invoked programmatically:

```cpp
if (ctx.project->getAssets().pollWatch()) {
    ctx.project->getAssets().reload();  // Clears vectors, frees shared_ptrs
    // Assets re-scanned and re-loaded automatically
}

```

*Reference*: [`src/main.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/main.cpp) line 274

### Runtime Script Creation

New assets integrate seamlessly into the watching system upon creation:

```cpp
bool created = ctx.project->getAssets().createScript("MyNewScript", "scripts");
if (created) {
    // File appears on disk; next pollWatch() cycle picks it up automatically
}

```

*Reference*: [`src/project/assetManager.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/assetManager.h) lines 53-55

### Fallback Texture Handling

The manager provides a lazily-initialized fallback texture for failed loads, itself managed via `shared_ptr`:

```cpp
const std::shared_ptr<Renderer::Texture> &Project::AssetManager::getFallbackTexture() {
    if (!fallbackTex) fallbackTex = std::make_shared<Renderer::Texture>(/* ... */);
    return fallbackTex;
}

```

*Location*: [`src/project/assetManager.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/assetManager.h) lines 49-51

## Summary

- **Unified indexing**: Assets are cataloged at startup into type-specific vectors with UUID mapping for constant-time lookups
- **Automatic memory management**: GPU textures, meshes, and prefabs use `std::shared_ptr` ownership; clearing entries or erasing vectors triggers immediate destructor-based cleanup
- **Live watching**: `pollWatch()` scans every 2 seconds for file changes, handling incremental updates and removals without manual intervention
- **Safe access patterns**: The `getEntryByUUID()` API returns pointers to entries containing shared pointers, ensuring resources remain valid during access
- **Zero manual deletion**: No explicit `delete` calls are required anywhere in the asset pipeline

## Frequently Asked Questions

### How does Pyrite64 handle memory cleanup when assets are deleted from disk?

When `pollWatch()` detects a removed file, it erases the corresponding entry from the `entries` vector and `entriesMap`. Because these containers store `std::shared_ptr` objects, erasure destroys the last reference to the resource, automatically invoking the destructor to free GPU memory or heap allocations immediately.

### What is the file system polling interval in Pyrite64's AssetManager?

The `pollWatch()` method executes every 2 seconds by default, defined by the `kMinInterval` constant. This interval balances responsiveness to asset changes with minimal runtime overhead during editor sessions.

### How are GPU textures managed to prevent memory leaks?

GPU textures are instantiated exclusively within `reloadEntry()` as `std::shared_ptr<Renderer::Texture>` and stored in `AssetManagerEntry` structures. When entries are cleared during a full `reload()` or individually erased due to file deletion, the shared pointer reference count drops to zero, automatically destroying the `Renderer::Texture` object and its associated GPU resources.

### Can assets be queried efficiently at runtime without string path lookups?

Yes. The `AssetManager` maintains an `entriesMap` that maps 64-bit UUIDs to type-index pairs, enabling O(1) retrieval via `getEntryByUUID()`. This avoids expensive string comparisons or recursive directory searches during gameplay or editor operations.