Conversation
There was a problem hiding this comment.
Pull request overview
This PR targets reduced memory pressure and improved cleanup by refining disposal patterns and reusing allocations in rendering/editor paths, plus replacing texture hashing with a Blake3-based approach.
Changes:
- Added/adjusted disposal patterns (including event unsubscription and finalizer suppression) to reduce leaks and redundant finalization.
- Reduced allocations in geometry clearing, ghost block drawing, and room sorting by reusing buffers/caches.
- Replaced texture hashing with Blake3 hashing of dimensions + pixel data.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| TombLib/TombLib/Wad/WadTexture.cs | Switches texture hashing to Blake3 and removes MemoryStream-based hashing. |
| TombLib/TombLib/LevelData/RoomGeometry.cs | Attempts to preserve list capacity across clears to reduce re-allocations. |
| TombLib/TombLib/LevelData/ImportedGeometry.cs | Makes imported geometry textures disposable and disposes GPU textures when not reused. |
| TombLib/TombLib/IO/BinaryWriterFast.cs | Suppresses finalization after disposal. |
| TombLib/TombLib.Rendering/Rendering/RenderingFont.cs | Fixes dispose guard logic and suppresses finalization. |
| TombEditor/Controls/Panel3D/Panel3DDrawCollector.cs | Reuses a cached distance buffer for sorting rooms to draw. |
| TombEditor/Controls/Panel3D/Panel3DDraw.cs | Reuses a cached vertex array for ghost block rendering. |
| TombEditor/Controls/Panel3D/Panel3D.cs | Adds fields backing the new draw/sort caches. |
| TombEditor/Controls/ImportedGeometryManager.cs | Stores/removes a ListChanged handler to avoid event-handler leaks on dispose. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
TombLib/TombLib.Rendering/Rendering/RenderingFont.cs:323
RenderingFont.Dispose()now runs (the_disposedguard was fixed) and it disposesTextureAllocator. However,TextureAllocatoris provided by the caller viaRenderingFont.Description, and several call sites dispose the allocator separately (often before disposing the font). This makes ownership unclear and can lead to redundant/double disposal or ordering issues. Consider removing allocator disposal fromRenderingFont(caller-owned), or update all call sites to treat the allocator as font-owned and dispose only once (and in a consistent order).
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
TextureAllocator?.Dispose();
GDI.DeleteObject(_gdiFont);
TombLib/TombLib.Rendering/Rendering/RenderingFont.cs:335
- The finalizer
~RenderingFont()callsDispose(). Now thatDispose()performs full managed cleanup (including disposingTextureAllocator) and callsGC.SuppressFinalize, the finalizer thread will run that managed disposal path too. Consider switching to the standardDispose(bool disposing)pattern so the finalizer only releases unmanaged resources (and does not touch potentially already-finalized managed objects), and onlyDispose(true)disposes managed fields.
GC.SuppressFinalize(this);
}
~RenderingFont()
{
Dispose();
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Hash = Hash.FromByteArray(ms.ToArray()); | ||
| } | ||
| // Hash image dimensions and pixel data directly without intermediate copies. | ||
| using var hasher = Blake3Hasher.New(); |
There was a problem hiding this comment.
What's the advantage of Blake3 hasher over Hash.FromByteArray?
There was a problem hiding this comment.
Less heap allocation, no MemoryStream buffer, no BinaryWriter overhead, no final ToArray() copy, less GC pressure, slightly less CPU work. Both use Blake3, but this approach is more memory efficient with less overhead.
| else | ||
| SynchronizationContext.Current.Post(unused => // Synchronize DirectX, we can't 'send' because that may deadlock with the level settings reloader | ||
| DirectXTexture = TextureLoad.Load(ImportedGeometry.Device, Image), null); | ||
| SynchronizationContext.Current.Post(unused => { |
There was a problem hiding this comment.
I don't fully understand what's happening here. This code prevents re-initialization of already disposed texture in race conditions?
There was a problem hiding this comment.
Yes, this is a very rare, but potential order of execution issue, as the posted lambda may happen after the object has been disposed. Extremely rare, but still good to add a defense mechanism just in case.
|
|
||
| #region Destructor Region | ||
|
|
||
| ~ObservableList() |
There was a problem hiding this comment.
Please explain why Dispose methods were removed for this control. It's important because it's outside of TE codebase.
There was a problem hiding this comment.
The Dispose() methods weren't removed, they were restructured. The destructor was calling Dispose() for no reason, so the code has been simplified.
| _startPtr = null; | ||
| _baseStream = null; | ||
|
|
||
| GC.SuppressFinalize(this); |
There was a problem hiding this comment.
What's the purpose of this code?
There was a problem hiding this comment.
It avoids an unnecessary destructor call.
if (_handle.IsAllocated)
_handle.Free();Is already called in the Dispose() method, so the destructor does not have to be called again, saving one extra call. It also eases GC pressure ever so slightly, as you are marking it as "Hey, this is safe to be removed from memory now, no need to put it into the finalizer queue."
|
@copilot Could you please verify this PR is production ready and that there are no regressions because of the changes. |
…lReferenceException Agent-Logs-Url: https://github.com/TombEngine/Tomb-Editor/sessions/a17e74ac-eb6d-41c7-9862-469fbbc04719 Co-authored-by: Nickelony <20436882+Nickelony@users.noreply.github.com>
I've verified the PR is production-ready. Key findings: Code Analysis:
Fix Applied (cca21b8): Build Status: No Regressions:
|
Addresses memory leaks and GC pressure from improper disposal patterns, missing event unsubscription, and redundant allocations in rendering paths.
Resource Management
Dispose()for no reasonReleaseUnmanagedResources()for proper destructor handling, addGC.SuppressFinalizeGC.SuppressFinalizeto skip redundant finalizer queueIDisposablewith_disposedguard to prevent GPU texture allocation after disposal in asyncSynchronizationContext.PostcallbackDisposeto preventNullReferenceExceptionin design-time or early disposalEvent Handler Cleanup
ListChangedhandler reference for explicit unsubscription during disposalRendering Optimizations
_ghostBlockVerticesarray and_roomsDistanceCacheto eliminate per-frame allocationsRenderingFont.Dispose()now ownsTextureAllocator, remove redundant_fontTexture?.Dispose()callsTexture Hashing
Replaced
BinaryWriter+MemoryStream+ToArray()with direct Blake3 hashing: