Skip to content

Commit c83c363

Browse files
committed
strategies are not dispoable anymore but can be cleaned. Make LiveVirtualStrategy is faster.
1 parent c3702a9 commit c83c363

5 files changed

Lines changed: 40 additions & 38 deletions

File tree

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/IO/FileExistStrategies/FileExistsStrategy.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
namespace PG.StarWarsGame.Engine.IO.FileExistStrategies;
66

7-
internal abstract class FileExistsStrategy(IFileSystem fileSystem) : IDisposable
7+
internal abstract class FileExistsStrategy(IFileSystem fileSystem)
88
{
99
protected readonly IFileSystem FileSystem = fileSystem;
1010

1111
public abstract bool FileExists(ReadOnlySpan<char> baseDirectory, ref ValueStringBuilder stringBuilder);
1212

13-
public virtual void Dispose() { }
13+
internal virtual void Cleanup() { }
1414
}

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/IO/FileExistStrategies/LiveVirtualFileExistsStrategy.cs

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System;
2-
using System.Collections.Generic;
2+
using System.Collections.Concurrent;
33
using System.IO;
44
using System.IO.Abstractions;
55
using AnakinRaW.CommonUtilities.FileSystem;
@@ -13,8 +13,7 @@ internal sealed class LiveVirtualFileExistsStrategy(IFileSystem fileSystem, File
1313
private const int WatcherBufferSize = 64 * 1024;
1414

1515
private readonly object _watchersLock = new();
16-
private readonly Dictionary<string, IFileSystemWatcher> _watchers = new(StringComparer.OrdinalIgnoreCase);
17-
private bool _disposed;
16+
private readonly ConcurrentDictionary<string, IFileSystemWatcher> _watchers = new(StringComparer.OrdinalIgnoreCase);
1817

1918
public override bool FileExists(ReadOnlySpan<char> baseDirectory, ref ValueStringBuilder stringBuilder)
2019
{
@@ -23,18 +22,37 @@ public override bool FileExists(ReadOnlySpan<char> baseDirectory, ref ValueStrin
2322
return base.FileExists(baseDirectory, ref stringBuilder);
2423
}
2524

25+
internal override void Cleanup()
26+
{
27+
IFileSystemWatcher[] watchers;
28+
lock (_watchersLock)
29+
{
30+
watchers = new IFileSystemWatcher[_watchers.Count];
31+
_watchers.Values.CopyTo(watchers, 0);
32+
_watchers.Clear();
33+
}
34+
35+
foreach (var watcher in watchers)
36+
TearDownWatcher(watcher);
37+
38+
base.Cleanup();
39+
}
40+
2641
private void EnsureWatcher(ReadOnlySpan<char> baseDirectory)
2742
{
28-
if (_disposed)
43+
var rootStr = baseDirectory.ToString();
44+
45+
// Fast path: already watching this directory — lockless, no OS call.
46+
if (_watchers.ContainsKey(rootStr))
2947
return;
3048

31-
var rootStr = baseDirectory.ToString();
49+
// Only pay for the Directory.Exists syscall when the watcher might be missing.
3250
if (!FileSystem.Directory.Exists(rootStr))
3351
return;
3452

3553
lock (_watchersLock)
3654
{
37-
if (_disposed || _watchers.ContainsKey(rootStr))
55+
if (_watchers.ContainsKey(rootStr))
3856
return;
3957

4058
var watcher = FileSystem.FileSystemWatcher.New(rootStr);
@@ -53,25 +71,6 @@ private void EnsureWatcher(ReadOnlySpan<char> baseDirectory)
5371
}
5472
}
5573

56-
public override void Dispose()
57-
{
58-
IFileSystemWatcher[] watchers;
59-
lock (_watchersLock)
60-
{
61-
if (_disposed)
62-
return;
63-
_disposed = true;
64-
watchers = new IFileSystemWatcher[_watchers.Count];
65-
_watchers.Values.CopyTo(watchers, 0);
66-
_watchers.Clear();
67-
}
68-
69-
foreach (var watcher in watchers)
70-
TearDownWatcher(watcher);
71-
72-
base.Dispose();
73-
}
74-
7574
private void OnFileEvent(object sender, FileSystemEventArgs e)
7675
{
7776
InvalidatePathAndSubtree(e.FullPath);
@@ -89,8 +88,6 @@ private void OnWatcherError(object sender, ErrorEventArgs e)
8988
string? brokenRoot = null;
9089
lock (_watchersLock)
9190
{
92-
if (_disposed)
93-
return;
9491
foreach (var kv in _watchers)
9592
{
9693
if (ReferenceEquals(kv.Value, sender))
@@ -102,7 +99,7 @@ private void OnWatcherError(object sender, ErrorEventArgs e)
10299
}
103100
if (broken is null)
104101
return;
105-
_watchers.Remove(brokenRoot!);
102+
_watchers.TryRemove(brokenRoot!, out _);
106103
}
107104

108105
ClearCacheUnder(brokenRoot!);

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/IO/FileExistStrategies/VirtualFileExistsStrategyBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ internal abstract class VirtualFileExistsStrategyBase(IFileSystem fileSystem, Fi
1515

1616
protected readonly FileExistsStrategy Underlying = underlying;
1717

18-
public override void Dispose()
18+
internal override void Cleanup()
1919
{
2020
Store.Clear();
21-
Underlying.Dispose();
21+
Underlying.Cleanup();
2222
}
2323

2424
public override bool FileExists(ReadOnlySpan<char> baseDirectory, ref ValueStringBuilder stringBuilder)

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/IO/PetroglyphFileSystem.Strategies.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ public sealed partial class PetroglyphFileSystem
88
{
99
private FileExistsStrategy _strategy;
1010

11+
internal void CleanupStrategy() => _strategy.Cleanup();
12+
13+
private FileExistsStrategy CreateDefaultStrategy()
14+
{
15+
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
16+
? new WindowsFileExistsStrategy(_underlyingFileSystem)
17+
: new VirtualFileExistsStrategy(_underlyingFileSystem, new WineFileExistsStrategy(_underlyingFileSystem));
18+
}
19+
1120
/// <summary>
1221
/// Switches the active file-exists strategy to one that issues a Win32 <c>CreateFileA</c> call per lookup.
1322
/// </summary>
@@ -116,6 +125,6 @@ private void SwapStrategy(FileExistsStrategy next)
116125
{
117126
var old = _strategy;
118127
_strategy = next;
119-
old?.Dispose();
128+
old.Cleanup();
120129
}
121130
}

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/IO/PetroglyphFileSystem.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
using System.IO;
55
using System.IO.Abstractions;
66
using System.Runtime.CompilerServices;
7-
using System.Runtime.InteropServices;
8-
using PG.StarWarsGame.Engine.IO.FileExistStrategies;
97

108
namespace PG.StarWarsGame.Engine.IO;
119

@@ -47,9 +45,7 @@ public PetroglyphFileSystem(IServiceProvider serviceProvider)
4745
throw new ArgumentNullException(nameof(serviceProvider));
4846
_underlyingFileSystem = serviceProvider.GetRequiredService<IFileSystem>();
4947

50-
_strategy = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
51-
? new WindowsFileExistsStrategy(_underlyingFileSystem)
52-
: new VirtualFileExistsStrategy(_underlyingFileSystem, new WineFileExistsStrategy(_underlyingFileSystem));
48+
_strategy = CreateDefaultStrategy();
5349
}
5450

5551
/// <summary>

0 commit comments

Comments
 (0)