11using System ;
2+ using System . Collections . Concurrent ;
23using System . Collections . Generic ;
34using System . Numerics ;
45using Robust . Client . UserInterface . CustomControls ;
@@ -15,10 +16,14 @@ internal sealed partial class Clyde
1516 private readonly Dictionary < ClydeHandle , WeakReference < Viewport > > _viewports =
1617 new ( ) ;
1718
19+ private long _nextViewportId = 1 ;
20+
21+ private readonly ConcurrentQueue < ViewportDisposeData > _viewportDisposeQueue = new ( ) ;
22+
1823 private Viewport CreateViewport ( Vector2i size , TextureSampleParameters ? sampleParameters = default , string ? name = null )
1924 {
2025 var handle = AllocRid ( ) ;
21- var viewport = new Viewport ( handle , name , this )
26+ var viewport = new Viewport ( _nextViewportId ++ , handle , name , this )
2227 {
2328 Size = size ,
2429 RenderTarget = CreateRenderTarget ( size ,
@@ -59,27 +64,42 @@ private static Vector2 ScreenToMap(Vector2 point, Viewport vp)
5964
6065 private void FlushViewportDispose ( )
6166 {
62- // Free of allocations unless a dead viewport is found.
63- List < ClydeHandle > ? toRemove = null ;
64- foreach ( var ( handle , viewportRef ) in _viewports )
67+ while ( _viewportDisposeQueue . TryDequeue ( out var data ) )
6568 {
66- if ( ! viewportRef . TryGetTarget ( out _ ) )
67- {
68- toRemove ??= new List < ClydeHandle > ( ) ;
69- toRemove . Add ( handle ) ;
70- }
69+ DisposeViewport ( data ) ;
7170 }
71+ }
7272
73- if ( toRemove == null )
74- {
73+ private void DisposeViewport ( ViewportDisposeData disposeData )
74+ {
75+ _clydeSawmill . Warning ( $ "Viewport { disposeData . Id } got leaked") ;
76+
77+ _viewports . Remove ( disposeData . Handle ) ;
78+ if ( disposeData . ClearEvent is not { } clearEvent )
7579 return ;
80+
81+ try
82+ {
83+ clearEvent ( disposeData . ClearEventData ) ;
84+ }
85+ catch ( Exception ex )
86+ {
87+ _clydeSawmill . Error ( $ "Caught exception while disposing viewport: { ex } ") ;
7688 }
89+ }
7790
78- foreach ( var remove in toRemove )
91+ #if TOOLS
92+ public void ViewportsClearAllCached ( )
93+ {
94+ foreach ( var vpRef in _viewports . Values )
7995 {
80- _viewports . Remove ( remove ) ;
96+ if ( ! vpRef . TryGetTarget ( out var vp ) )
97+ continue ;
98+
99+ vp . FireClear ( ) ;
81100 }
82101 }
102+ #endif // TOOLS
83103
84104 private sealed class Viewport : IClydeViewport
85105 {
@@ -106,17 +126,20 @@ private sealed class Viewport : IClydeViewport
106126
107127 public string ? Name { get ; }
108128
109- public Viewport ( ClydeHandle handle , string ? name , Clyde clyde )
129+ public Viewport ( long id , ClydeHandle handle , string ? name , Clyde clyde )
110130 {
111131 Name = name ;
112132 _handle = handle ;
113133 _clyde = clyde ;
134+ Id = id ;
114135 }
115136
116137 public Vector2i Size { get ; set ; }
138+ public event Action < ClearCachedViewportResourcesEvent > ? ClearCachedResources ;
117139 public Color ? ClearColor { get ; set ; } = Color . Black ;
118140 public Vector2 RenderScale { get ; set ; } = Vector2 . One ;
119141 public bool AutomaticRender { get ; set ; }
142+ public long Id { get ; }
120143
121144 void IClydeViewport . Render ( )
122145 {
@@ -186,20 +209,56 @@ public void RenderScreenOverlaysAbove(
186209 _clyde . RenderOverlaysDirect ( this , control , handle , OverlaySpace . ScreenSpace , viewportBounds ) ;
187210 }
188211
212+ ~ Viewport ( )
213+ {
214+ _clyde . _viewportDisposeQueue . Enqueue ( DisposeData ( referenceSelf : false ) ) ;
215+ }
216+
189217 public void Dispose ( )
190218 {
219+ GC . SuppressFinalize ( this ) ;
220+
191221 RenderTarget . Dispose ( ) ;
192222 LightRenderTarget . Dispose ( ) ;
193223 WallMaskRenderTarget . Dispose ( ) ;
194224 WallBleedIntermediateRenderTarget1 . Dispose ( ) ;
195225 WallBleedIntermediateRenderTarget2 . Dispose ( ) ;
196226
197- _clyde . _viewports . Remove ( _handle ) ;
227+ _clyde . DisposeViewport ( DisposeData ( referenceSelf : false ) ) ;
228+ }
229+
230+ private ViewportDisposeData DisposeData ( bool referenceSelf )
231+ {
232+ return new ViewportDisposeData
233+ {
234+ Handle = _handle ,
235+ Id = Id ,
236+ ClearEvent = ClearCachedResources ,
237+ ClearEventData = MakeClearEvent ( referenceSelf )
238+ } ;
239+ }
240+
241+ private ClearCachedViewportResourcesEvent MakeClearEvent ( bool referenceSelf )
242+ {
243+ return new ClearCachedViewportResourcesEvent ( Id , referenceSelf ? this : null ) ;
244+ }
245+
246+ public void FireClear ( )
247+ {
248+ ClearCachedResources ? . Invoke ( MakeClearEvent ( referenceSelf : true ) ) ;
198249 }
199250
200251 IRenderTexture IClydeViewport . RenderTarget => RenderTarget ;
201252 IRenderTexture IClydeViewport . LightRenderTarget => LightRenderTarget ;
202253 public IEye ? Eye { get ; set ; }
203254 }
255+
256+ private sealed class ViewportDisposeData
257+ {
258+ public ClydeHandle Handle ;
259+ public long Id ;
260+ public Action < ClearCachedViewportResourcesEvent > ? ClearEvent ;
261+ public ClearCachedViewportResourcesEvent ClearEventData ;
262+ }
204263 }
205264}
0 commit comments