11using System . Runtime . InteropServices ;
22using System . Timers ;
33using System . Diagnostics ;
4+ using System . ComponentModel ;
45
56namespace Bloxstrap . Integrations
67{
@@ -9,6 +10,7 @@ public class MemoryCleaner : IDisposable
910 private const string LOG_IDENT = "MemoryCleaner" ;
1011
1112 private System . Timers . Timer ? _cleanupTimer ;
13+ private System . Timers . Timer ? _robloxTrimTimer ;
1214 private bool _isCleaning = false ;
1315 private readonly object _cleanupLock = new object ( ) ;
1416
@@ -18,7 +20,6 @@ public class MemoryCleaner : IDisposable
1820 [ DllImport ( "psapi.dll" , SetLastError = true ) ]
1921 private static extern int EmptyWorkingSet ( IntPtr hProcess ) ;
2022
21-
2223 [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
2324 private static extern IntPtr GetCurrentProcess ( ) ;
2425
@@ -28,6 +29,8 @@ public class MemoryCleaner : IDisposable
2829 "smss" , "svchost" , "explorer" , "taskhostw" , "dwm" , "ctfmon"
2930 } ;
3031
32+ private readonly HashSet < string > _robloxProcessNames = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) { "robloxplayerbeta" , "robloxstudiobeta" } ;
33+
3134 public MemoryCleaner ( )
3235 {
3336 InitializeTimer ( ) ;
@@ -40,6 +43,7 @@ private void InitializeTimer()
4043 try
4144 {
4245 UpdateTimer ( ) ;
46+ UpdateRobloxTrimTimer ( ) ;
4347 App . Logger . WriteLine ( LOG_IDENT_INIT , "Memory cleaner initialized" ) ;
4448 }
4549 catch ( Exception ex )
@@ -71,6 +75,66 @@ private void UpdateTimer()
7175 App . Logger . WriteLine ( $ "{ LOG_IDENT } ::UpdateTimer", $ "Timer started with { interval } interval") ;
7276 }
7377
78+ private void UpdateRobloxTrimTimer ( )
79+ {
80+ _robloxTrimTimer ? . Stop ( ) ;
81+ _robloxTrimTimer ? . Dispose ( ) ;
82+ _robloxTrimTimer = null ;
83+
84+ if ( ! App . Settings . Prop . EnableRobloxTrim || App . Settings . Prop . RobloxTrimIntervalSeconds <= 0 )
85+ {
86+ App . Logger . WriteLine ( $ "{ LOG_IDENT } ::UpdateRobloxTrimTimer", "Roblox-specific trimming disabled" ) ;
87+ return ;
88+ }
89+
90+ int intervalSeconds = App . Settings . Prop . RobloxTrimIntervalSeconds ;
91+
92+ _robloxTrimTimer = new System . Timers . Timer ( ) ;
93+ _robloxTrimTimer . Interval = intervalSeconds * 1000 ;
94+ _robloxTrimTimer . Elapsed += OnRobloxTrimTimerElapsed ;
95+ _robloxTrimTimer . AutoReset = true ;
96+ _robloxTrimTimer . Start ( ) ;
97+
98+ App . Logger . WriteLine ( $ "{ LOG_IDENT } ::UpdateRobloxTrimTimer", $ "Roblox trim timer started with { intervalSeconds } second interval") ;
99+ }
100+
101+ private void OnRobloxTrimTimerElapsed ( object ? sender , ElapsedEventArgs e )
102+ {
103+ const string LOG_IDENT_TIMER = $ "{ LOG_IDENT } ::OnRobloxTrimTimerElapsed";
104+
105+ try
106+ {
107+ if ( _isCleaning )
108+ {
109+ App . Logger . WriteLine ( LOG_IDENT_TIMER , "Cleanup already in progress, skipping Roblox trim..." ) ;
110+ return ;
111+ }
112+
113+ App . Logger . WriteLine ( LOG_IDENT_TIMER , "Roblox trim timer elapsed, trimming Roblox processes" ) ;
114+
115+ lock ( _cleanupLock )
116+ {
117+ _isCleaning = true ;
118+ }
119+
120+ try
121+ {
122+ TrimRobloxProcesses ( ) ;
123+ }
124+ finally
125+ {
126+ lock ( _cleanupLock )
127+ {
128+ _isCleaning = false ;
129+ }
130+ }
131+ }
132+ catch ( Exception ex )
133+ {
134+ App . Logger . WriteLine ( LOG_IDENT_TIMER , $ "Exception: { ex . Message } ") ;
135+ }
136+ }
137+
74138 private void OnCleanupTimerElapsed ( object ? sender , ElapsedEventArgs e )
75139 {
76140 const string LOG_IDENT_TIMER = $ "{ LOG_IDENT } ::OnCleanupTimerElapsed";
@@ -110,7 +174,6 @@ public void CleanMemory()
110174 App . Logger . WriteLine ( LOG_IDENT_CLEAN , $ "Total memory before cleanup: { FormatBytes ( beforeMemory ) } ") ;
111175
112176 CleanStandbyList ( ) ;
113-
114177 CleanProcessWorkingSets ( ) ;
115178
116179 long afterMemory = GetTotalMemoryUsage ( ) ;
@@ -135,6 +198,159 @@ public void CleanMemory()
135198 }
136199 }
137200
201+ public void TrimRobloxProcesses ( )
202+ {
203+ const string LOG_IDENT_ROBLOX = $ "{ LOG_IDENT } ::TrimRobloxProcesses";
204+
205+ try
206+ {
207+ int robloxProcessesFound = 0 ;
208+ int robloxProcessesTrimmed = 0 ;
209+ long totalRobloxMemoryFreed = 0 ;
210+
211+ foreach ( var process in Process . GetProcesses ( ) )
212+ {
213+ try
214+ {
215+ string processName = process . ProcessName . ToLower ( ) ;
216+
217+ if ( _robloxProcessNames . Contains ( processName ) && ! process . HasExited )
218+ {
219+ robloxProcessesFound ++ ;
220+
221+ long beforeMemory = process . WorkingSet64 ;
222+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX ,
223+ $ "Found Roblox process: { process . ProcessName } (PID: { process . Id } ) using { FormatBytes ( beforeMemory ) } ") ;
224+
225+ if ( TrimRobloxProcessMemory ( process ) )
226+ {
227+ process . Refresh ( ) ;
228+ long afterMemory = process . WorkingSet64 ;
229+ long memoryFreed = beforeMemory - afterMemory ;
230+
231+ if ( memoryFreed > 0 )
232+ {
233+ robloxProcessesTrimmed ++ ;
234+ totalRobloxMemoryFreed += memoryFreed ;
235+
236+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX ,
237+ $ "Trimmed Roblox process { process . ProcessName } : { FormatBytes ( memoryFreed ) } freed") ;
238+ }
239+ }
240+ }
241+ }
242+ catch ( Exception ex )
243+ {
244+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX , $ "Error processing Roblox process: { ex . Message } ") ;
245+ }
246+ finally
247+ {
248+ process . Dispose ( ) ;
249+ }
250+ }
251+
252+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX ,
253+ $ "Roblox trimming complete: { robloxProcessesTrimmed } /{ robloxProcessesFound } processes trimmed, " +
254+ $ "total freed: { FormatBytes ( totalRobloxMemoryFreed ) } ") ;
255+ }
256+ catch ( Exception ex )
257+ {
258+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX , $ "Exception trimming Roblox processes: { ex . Message } ") ;
259+ }
260+ }
261+
262+ private bool TrimRobloxProcessMemory ( Process process )
263+ {
264+ const string LOG_IDENT_ROBLOX_TRIM = $ "{ LOG_IDENT } ::TrimRobloxProcessMemory";
265+
266+ try
267+ {
268+ if ( process . HasExited )
269+ return false ;
270+
271+ if ( ! process . Responding )
272+ {
273+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX_TRIM ,
274+ $ "Roblox process { process . ProcessName } (PID: { process . Id } ) is not responding") ;
275+ return false ;
276+ }
277+
278+ bool success = true ;
279+
280+ try
281+ {
282+ SetProcessWorkingSetSize ( process . Handle , ( IntPtr ) ( - 1 ) , ( IntPtr ) ( - 1 ) ) ;
283+ }
284+ catch ( Exception ex )
285+ {
286+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX_TRIM ,
287+ $ "SetProcessWorkingSetSize failed for { process . ProcessName } : { ex . Message } ") ;
288+ success = false ;
289+ }
290+
291+ try
292+ {
293+ EmptyWorkingSet ( process . Handle ) ;
294+ }
295+ catch ( Exception ex )
296+ {
297+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX_TRIM ,
298+ $ "EmptyWorkingSet failed for { process . ProcessName } : { ex . Message } ") ;
299+ success = false ;
300+ }
301+
302+ Thread . Sleep ( 50 ) ;
303+
304+ return success ;
305+ }
306+ catch ( Exception ex )
307+ {
308+ App . Logger . WriteLine ( LOG_IDENT_ROBLOX_TRIM ,
309+ $ "Failed to trim Roblox process { process . ProcessName } (PID: { process . Id } ): { ex . Message } ") ;
310+ return false ;
311+ }
312+ }
313+
314+ public void TrimRobloxNow ( )
315+ {
316+ const string LOG_IDENT_MANUAL = $ "{ LOG_IDENT } ::TrimRobloxNow";
317+
318+ lock ( _cleanupLock )
319+ {
320+ if ( _isCleaning )
321+ {
322+ App . Logger . WriteLine ( LOG_IDENT_MANUAL , "Cleanup already in progress, skipping manual Roblox trim" ) ;
323+ return ;
324+ }
325+
326+ _isCleaning = true ;
327+ }
328+
329+ try
330+ {
331+ App . Logger . WriteLine ( LOG_IDENT_MANUAL , "Manual Roblox memory trim requested" ) ;
332+ TrimRobloxProcesses ( ) ;
333+ OnRobloxTrimmed ? . Invoke ( this , EventArgs . Empty ) ;
334+ }
335+ catch ( Exception ex )
336+ {
337+ App . Logger . WriteLine ( LOG_IDENT_MANUAL , $ "Exception during manual Roblox trim: { ex . Message } ") ;
338+ OnCleanupError ? . Invoke ( this , ex . Message ) ;
339+ }
340+ finally
341+ {
342+ lock ( _cleanupLock )
343+ {
344+ _isCleaning = false ;
345+ }
346+ }
347+ }
348+
349+ public void RefreshRobloxTimer ( )
350+ {
351+ UpdateRobloxTrimTimer ( ) ;
352+ }
353+
138354 private void CleanStandbyList ( )
139355 {
140356 const string LOG_IDENT_STANDBY = $ "{ LOG_IDENT } ::CleanStandbyList";
@@ -219,6 +435,9 @@ private bool IsProcessSafeToClean(Process process)
219435
220436 string processName = process . ProcessName . ToLower ( ) ;
221437
438+ if ( _robloxProcessNames . Contains ( processName ) )
439+ return false ;
440+
222441 if ( _criticalProcesses . Contains ( processName ) )
223442 return false ;
224443
@@ -329,6 +548,8 @@ public void Dispose()
329548 {
330549 _cleanupTimer ? . Stop ( ) ;
331550 _cleanupTimer ? . Dispose ( ) ;
551+ _robloxTrimTimer ? . Stop ( ) ;
552+ _robloxTrimTimer ? . Dispose ( ) ;
332553 App . Logger . WriteLine ( LOG_IDENT_DISPOSE , "Memory cleaner disposed" ) ;
333554 }
334555 catch ( Exception ex )
@@ -339,5 +560,6 @@ public void Dispose()
339560
340561 public event EventHandler < long > ? OnMemoryCleaned ;
341562 public event EventHandler < string > ? OnCleanupError ;
563+ public event EventHandler ? OnRobloxTrimmed ;
342564 }
343565}
0 commit comments