| name | resource-management-audit |
| description | Audit resource management including IDisposable pattern implementation, proper cleanup of OpenGL resources (buffers, textures, shaders, framebuffers), memory leak detection, resource lifetime management, and GPU resource tracking. Use when investigating memory leaks, GPU resource exhaustion, or implementing new resource types. |
Resource Management Audit
Overview
This skill audits resource management to ensure proper cleanup of OpenGL resources, correct IDisposable implementation, and prevention of memory leaks. Focus on engine-specific concerns like OpenGL context safety, factory ownership patterns, and GPU resource tracking.
When to Use
Invoke this skill when:
- Investigating memory leaks or growing memory usage
- GPU resources (textures, buffers) are not being cleaned up
- Adding new resource types (textures, shaders, meshes, framebuffers)
- Refactoring resource lifetime management
- Application crashes on shutdown (disposal issues)
- Out-of-memory errors or GPU resource exhaustion
- Reviewing code for proper disposal patterns
Critical Engine-Specific Rules
1. Never Call OpenGL in Finalizers
Why: OpenGL context is NOT available on finalizer thread. Calling GL functions in finalizers causes crashes.
// ✅ CORRECT - Log warning instead
~Texture()
{
if (_rendererID != 0)
{
Logger.Error($"Texture {_path} not disposed! GPU leak.");
// Do NOT call GL.DeleteTexture here!
}
}
public void Dispose()
{
if (_rendererID != 0)
{
GL.DeleteTexture(_rendererID); // Safe - correct thread
_rendererID = 0;
}
GC.SuppressFinalize(this); // Prevent finalizer
}
2. Factory-Managed Resources
Rule: Factory owns cached resources. Consumers get references but don't dispose them.
// TextureFactory owns and disposes cached textures
var texture = _textureFactory.Load("sprite.png");
// ❌ WRONG - Don't dispose factory-managed resources
texture.Dispose(); // Other users still need this!
// ✅ CORRECT - Factory disposes on shutdown
// Just use the texture, factory handles lifetime
Check ownership documentation in resource classes (see XML comments).
3. Shared vs. Owned Resources
Owned: Component creates resource exclusively for itself → Component disposes it Shared: Resource comes from factory/cache → Factory disposes it
public class MeshRenderer : IDisposable
{
private Mesh _mesh; // Shared (factory-managed)
private uint _instanceVBO; // Owned (created by this component)
public void Dispose()
{
// Don't dispose _mesh (shared)
// DO dispose _instanceVBO (owned)
if (_instanceVBO != 0)
{
GL.DeleteBuffer(_instanceVBO);
_instanceVBO = 0;
}
}
}
4. Disposal Guards
Always implement these safeguards:
public class Texture : IDisposable
{
private uint _rendererID;
private bool _disposed = false;
public void Dispose()
{
if (_disposed) // 1. Guard double-disposal
return;
if (_rendererID != 0) // 2. Check resource exists
{
GL.DeleteTexture(_rendererID);
_rendererID = 0; // 3. Reset to prevent re-delete
}
_disposed = true;
GC.SuppressFinalize(this); // 4. Skip finalizer
}
}
Audit Checklist
When reviewing resource-owning classes, verify:
IDisposable Implementation
- Implements
IDisposable - Has
_disposedflag to guard double-disposal - Resets resource IDs to 0 after deletion
- Calls
GC.SuppressFinalize(this)in Dispose() - Finalizer logs warning (never calls OpenGL)
Ownership Clarity
- Documented who owns the resource (XML comments)
- Shared resources NOT disposed by consumers
- Factory/pool manages disposal of cached resources
- Clear distinction between owned vs. referenced resources
Resource Creation Sites
- Every
new Texture()has clear disposal path - Every
GL.GenBuffer()has matchingGL.DeleteBuffer() - Resources created in loops are disposed or cached
- Exception handling doesn't skip disposal (use
usingor try-finally)
Testing
- Unit tests verify cleanup after load/unload cycles
- ResourceTracker shows stable counters (no growth)
- Shutdown check confirms zero leaks
- Manual testing with RenderDoc (GPU resources)
Common Anti-Patterns
See references/anti-patterns.cs for detailed examples. Quick reference:
- Missing Disposal: Resource created but never disposed → Add disposal path
- Double Disposal: No
_disposedguard → Crashes on second Dispose() - Disposing Shared Resources: Multiple owners unclear → Document ownership
- OpenGL in Finalizers: Crashes due to missing context → Log warning instead
- Forgetting GC.SuppressFinalize: Unnecessary finalizer runs → Always call it
- Wrong Disposal Order: FBO before attachments → Delete in reverse order of creation
- Null Checks Missing:
_mesh.Dispose()crashes if null → Use_mesh?.Dispose() - No Disposal Path: Resources in loops → Cache or use
usingstatement
Disposal Patterns Reference
Standard IDisposable patterns with engine-specific notes: references/disposal-patterns.cs
Pattern Selection:
- Basic Pattern: Sealed classes, OpenGL-only resources
- Full Pattern: Inheritable classes, mixed managed/unmanaged resources
- Factory Pattern: Cached/shared resources with centralized lifetime management
Output Format
When reporting audit findings:
**Issue**: [Resource management problem]
**Location**: [File:line]
**Resource Type**: [OpenGL buffer/texture/shader/etc.]
**Problem**: [Specific issue - leak, double disposal, missing cleanup]
**Recommendation**:
[Code example showing fix]
**Priority**: [Critical/High/Medium/Low]
Example:
**Issue**: Mesh resources not disposed when entity destroyed
**Location**: Engine/Scene/Entity.cs:89
**Resource Type**: OpenGL VBO, EBO, VAO
**Problem**: Entity.Destroy() doesn't dispose MeshComponent.Mesh,
causing GPU memory leak (20MB per load/unload cycle)
**Recommendation**:
public void Destroy()
{
if (HasComponent<MeshComponent>())
{
var meshComp = GetComponent<MeshComponent>();
// Only dispose if component owns the mesh (not factory-managed)
if (meshComp.OwnsMesh)
{
meshComp.Mesh?.Dispose();
}
meshComp.Mesh = null;
}
_scene.RemoveEntity(this);
}
**Priority**: High (GPU memory leak)
Key Files Reference
references/disposal-patterns.cs: Standard IDisposable patterns (basic, full, factory)references/anti-patterns.cs: 8 common mistakes with ❌/✅ examples
Summary
Core Principles:
- Never call OpenGL in finalizers (no GL context available)
- Document ownership explicitly (owned vs. shared resources)
- Factory-managed resources: factory disposes, consumers reference
- Always guard double-disposal with
_disposedflag - Always call
GC.SuppressFinalize(this)in Dispose() - Use ResourceTracker for leak detection in DEBUG builds
- Test cleanup with load/unload cycles and shutdown checks