diff --git a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs index 400f967a..da919272 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs @@ -55,6 +55,11 @@ namespace LibHac.FsSystem private ISaveDataCommitTimeStampGetter _timeStampGetter; private RandomDataGenerator _randomGenerator; + // Additions to support caching + private ISaveDataExtraDataAccessorCacheObserver _cacheObserver; + private SaveDataSpaceId _spaceId; + private ulong _saveDataId; + private class DirectorySaveDataFile : IFile { private IFile _baseFile; @@ -171,6 +176,7 @@ namespace LibHac.FsSystem protected override void Dispose(bool disposing) { + _cacheObserver?.Unregister(_spaceId, _saveDataId); _baseFs?.Dispose(); base.Dispose(disposing); } @@ -713,7 +719,7 @@ namespace LibHac.FsSystem private Result UpdateExtraDataTimeStamp() { Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - + Result rc = ReadExtraDataImpl(out SaveDataExtraData extraData); if (rc.IsFailure()) return rc; @@ -797,7 +803,12 @@ namespace LibHac.FsSystem public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) { - throw new NotImplementedException(); + _cacheObserver = observer; + _spaceId = spaceId; + _saveDataId = saveDataId; } + + public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId; + public ulong GetSaveDataId() => _saveDataId; } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs b/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs index acfe5ba1..2a512d24 100644 --- a/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs +++ b/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs @@ -1,14 +1,18 @@ using System; using LibHac.Fs; +using LibHac.Fs.Fsa; using LibHac.FsSystem.Save; namespace LibHac.FsSystem { public interface ISaveDataFileSystemCacheManager : IDisposable { - bool GetCache(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, ulong saveDataId); + bool GetCache(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, ulong saveDataId); void Register(ReferenceCountedDisposable fileSystem); void Register(ReferenceCountedDisposable fileSystem); void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); + + // LibHac addition + void Register(ReferenceCountedDisposable fileSystem); } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs b/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs new file mode 100644 index 00000000..77a92c4d --- /dev/null +++ b/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs @@ -0,0 +1,159 @@ +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem.Save; +using LibHac.Os; + +namespace LibHac.FsSystem +{ + public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager + { + private struct Cache + { + private ReferenceCountedDisposable _fileSystem; + private ulong _saveDataId; + private SaveDataSpaceId _spaceId; + + public void Dispose() + { + _fileSystem?.Dispose(); + _fileSystem = null; + } + + public bool IsCached(SaveDataSpaceId spaceId, ulong saveDataId) + { + return _fileSystem is not null && _spaceId == spaceId && _saveDataId == saveDataId; + } + + public ReferenceCountedDisposable Move() + { + return Shared.Move(ref _fileSystem); + } + + // Note: Nintendo only supports caching SaveDataFileSystem. We support DirectorySaveDataFileSystem too, + // so instead of calling methods on SaveDataFileSystem to get the save data info, + // we pass them in as parameters. + // Todo: Create a new interface for those methods? + public void Register(ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, + ulong saveDataId) + { + _spaceId = spaceId; + _saveDataId = saveDataId; + + _fileSystem?.Dispose(); + _fileSystem = fileSystem; + } + + public void Unregister() + { + _fileSystem?.Dispose(); + _fileSystem = null; + } + } + + private SdkRecursiveMutexType _mutex; + private Cache[] _cachedFileSystems; + private int _maxCachedFileSystemCount; + private int _nextCacheIndex; + + public SaveDataFileSystemCacheManager() + { + _mutex.Initialize(); + } + + public void Dispose() + { + Cache[] caches = _cachedFileSystems; + + for (int i = 0; i < caches.Length; i++) + { + caches[i].Dispose(); + } + } + + public Result Initialize(int maxCacheCount) + { + Assert.SdkRequiresGreaterEqual(maxCacheCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Assert.SdkAssert(_cachedFileSystems is null); + + _maxCachedFileSystemCount = maxCacheCount; + if (maxCacheCount <= 0) + return Result.Success; + + // Note: The original checks for overflow here + _cachedFileSystems = new Cache[maxCacheCount]; + return Result.Success; + } + + public bool GetCache(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, + ulong saveDataId) + { + Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _maxCachedFileSystemCount; i++) + { + if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) + { + fileSystem = _cachedFileSystems[i].Move(); + return true; + } + } + + fileSystem = default; + return false; + } + + public void Register(ReferenceCountedDisposable fileSystem) + { + throw new System.NotImplementedException(); + } + + public void Register(ReferenceCountedDisposable fileSystem) + { + throw new System.NotImplementedException(); + } + + public void Register(ReferenceCountedDisposable fileSystem) + { + if (_maxCachedFileSystemCount <= 0) + return; + + Assert.SdkRequiresGreaterEqual(_nextCacheIndex, 0); + Assert.SdkRequiresGreater(_maxCachedFileSystemCount, _nextCacheIndex); + + if (fileSystem.Target.GetSaveDataSpaceId() == SaveDataSpaceId.SdSystem) + return; + + Result rc = fileSystem.Target.Rollback(); + if (rc.IsFailure()) return; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + _cachedFileSystems[_nextCacheIndex].Register(fileSystem.AddReference(), + fileSystem.Target.GetSaveDataSpaceId(), fileSystem.Target.GetSaveDataId()); + + _nextCacheIndex = (_nextCacheIndex + 1) % _maxCachedFileSystemCount; + } + + public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) + { + Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _maxCachedFileSystemCount; i++) + { + if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) + { + _cachedFileSystems[i].Unregister(); + } + } + } + } +} \ No newline at end of file