From fcacbf5462668128d08e278914198870aa818ff5 Mon Sep 17 00:00:00 2001 From: Ivan Murashka Date: Sat, 22 Jun 2024 12:48:39 +0200 Subject: [PATCH] Add summary to BinaryPrefs and Builder (#33) --- .BinaryPrefs/Assets/Scenes/ExampleScene.unity | 12 + .../ProjectSettings/EditorSettings.asset | 21 +- .../ProjectSettings/ProjectSettings.asset | 36 +- Runtime/BinaryPrefs.Builder.cs | 71 +++- Runtime/BinaryPrefs.cs | 344 ++++++++++++++---- ...cs => CantSupportCollectionOfException.cs} | 4 +- ... CantSupportCollectionOfException.cs.meta} | 0 .../Serialization/CollectionTypeSerializer.cs | 5 - Runtime/Serialization/EnumTypeSerializer.cs | 1 - .../Serialization/KeyValueTypeSerializer.cs | 5 - .../Serialization/SystemTypesSerializers.cs | 24 +- Runtime/Serialization/TypeSerializer.cs | 1 - .../Serialization/UnityTypesSerializers.cs | 6 - .../BaseTypeSerializerTests.cs | 4 +- 14 files changed, 392 insertions(+), 142 deletions(-) rename Runtime/Exceptions/{CantSupportListOfException.cs => CantSupportCollectionOfException.cs} (71%) rename Runtime/Exceptions/{CantSupportListOfException.cs.meta => CantSupportCollectionOfException.cs.meta} (100%) diff --git a/.BinaryPrefs/Assets/Scenes/ExampleScene.unity b/.BinaryPrefs/Assets/Scenes/ExampleScene.unity index 48b1e2c..4148fd0 100644 --- a/.BinaryPrefs/Assets/Scenes/ExampleScene.unity +++ b/.BinaryPrefs/Assets/Scenes/ExampleScene.unity @@ -253,6 +253,14 @@ PrefabInstance: propertyPath: m_Name value: IngameDebugConsole objectReference: {fileID: 0} + - target: {fileID: 11440770, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} + propertyPath: m_ReferenceResolution.x + value: 1080 + objectReference: {fileID: 0} + - target: {fileID: 11440770, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} + propertyPath: m_ReferenceResolution.y + value: 1920 + objectReference: {fileID: 0} - target: {fileID: 22426080, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} propertyPath: m_AnchorMax.x value: 0 @@ -261,6 +269,10 @@ PrefabInstance: propertyPath: m_AnchorMax.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 22426080, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} - target: {fileID: 22428984, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} propertyPath: m_AnchorMax.y value: 0 diff --git a/.BinaryPrefs/ProjectSettings/EditorSettings.asset b/.BinaryPrefs/ProjectSettings/EditorSettings.asset index bcf5b76..9a1fcc1 100644 --- a/.BinaryPrefs/ProjectSettings/EditorSettings.asset +++ b/.BinaryPrefs/ProjectSettings/EditorSettings.asset @@ -3,8 +3,7 @@ --- !u!159 &1 EditorSettings: m_ObjectHideFlags: 0 - serializedVersion: 9 - m_ExternalVersionControlSupport: Visible Meta Files + serializedVersion: 11 m_SerializationMode: 2 m_LineEndingsForNewScripts: 0 m_DefaultBehaviorMode: 1 @@ -12,24 +11,34 @@ EditorSettings: m_PrefabUIEnvironment: {fileID: 0} m_SpritePackerMode: 4 m_SpritePackerPaddingPower: 1 + m_Bc7TextureCompressor: 0 m_EtcTextureCompressorBehavior: 1 m_EtcTextureFastCompressor: 1 m_EtcTextureNormalCompressor: 2 m_EtcTextureBestCompressor: 4 m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref - m_ProjectGenerationRootNamespace: Appegy - m_CollabEditorSettings: - inProgressEnabled: 1 + m_ProjectGenerationRootNamespace: Appegy.Storage.Example m_EnableTextureStreamingInEditMode: 1 m_EnableTextureStreamingInPlayMode: 1 m_AsyncShaderCompilation: 1 + m_CachingShaderPreprocessor: 1 + m_PrefabModeAllowAutoSave: 1 m_EnterPlayModeOptionsEnabled: 0 m_EnterPlayModeOptions: 3 - m_ShowLightmapResolutionOverlay: 1 + m_GameObjectNamingDigits: 1 + m_GameObjectNamingScheme: 0 + m_AssetNamingUsesSpace: 1 m_UseLegacyProbeSampleCount: 1 + m_SerializeInlineMappingsOnOneLine: 0 + m_DisableCookiesInLightmapper: 1 m_AssetPipelineMode: 1 + m_RefreshImportMode: 0 m_CacheServerMode: 0 m_CacheServerEndpoint: m_CacheServerNamespacePrefix: default m_CacheServerEnableDownload: 1 m_CacheServerEnableUpload: 1 + m_CacheServerEnableAuth: 0 + m_CacheServerEnableTls: 0 + m_CacheServerValidationMode: 2 + m_CacheServerDownloadBatchSize: 128 diff --git a/.BinaryPrefs/ProjectSettings/ProjectSettings.asset b/.BinaryPrefs/ProjectSettings/ProjectSettings.asset index 9b92f98..388a9fa 100644 --- a/.BinaryPrefs/ProjectSettings/ProjectSettings.asset +++ b/.BinaryPrefs/ProjectSettings/ProjectSettings.asset @@ -3,7 +3,7 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 23 + serializedVersion: 24 productGUID: ce495904e972ecb4197f94550b6a6432 AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 @@ -48,10 +48,11 @@ PlayerSettings: defaultScreenHeightWeb: 600 m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 + unsupportedMSAAFallback: 0 m_MTRendering: 1 mipStripping: 0 numberOfMipsStripped: 0 - m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + m_StackTraceTypes: 000000000000000000000000000000000000000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosUseCustomAppBackgroundBehavior: 0 @@ -74,6 +75,7 @@ PlayerSettings: androidMinimumWindowWidth: 400 androidMinimumWindowHeight: 300 androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 @@ -121,6 +123,7 @@ PlayerSettings: switchNVNOtherPoolsGranularity: 16777216 switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0 + switchMaxWorkerMultiple: 8 stadiaPresentMode: 0 stadiaTargetFramerate: 0 vulkanNumSwapchainBuffers: 3 @@ -155,7 +158,7 @@ PlayerSettings: androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: - Android: org.appegy.tools.ulog + Android: org.appegy.binaryprefs iPhone: org.appegy.tools.ulog buildNumber: Standalone: 0 @@ -178,10 +181,10 @@ PlayerSettings: StripUnusedMeshComponents: 1 VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 - iOSTargetOSVersionString: 11.0 + iOSTargetOSVersionString: 12.0 tvOSSdkVersion: 0 tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: 11.0 + tvOSTargetOSVersionString: 12.0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 @@ -534,7 +537,7 @@ PlayerSettings: switchSocketConcurrencyLimit: 14 switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 - switchUseGOLDLinker: 0 + switchEnableFileSystemTrace: 0 switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: @@ -611,7 +614,6 @@ PlayerSettings: switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 - switchTouchScreenUsage: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 switchApplicationErrorCodeCategory: @@ -653,6 +655,7 @@ PlayerSettings: switchNativeFsCacheSize: 32 switchIsHoldTypeHorizontal: 0 switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 switchSocketConfigEnabled: 0 switchTcpInitialSendBufferSize: 32 switchTcpInitialReceiveBufferSize: 64 @@ -663,8 +666,8 @@ PlayerSettings: switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 - switchPlayerConnectionEnabled: 1 switchUseNewStyleFilepaths: 0 + switchUseLegacyFmodPriorities: 1 switchUseMicroSleepForYield: 1 switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 @@ -770,13 +773,25 @@ PlayerSettings: platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {} - managedStrippingLevel: {} + managedStrippingLevel: + EmbeddedLinux: 1 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Lumin: 1 + Nintendo Switch: 1 + PS4: 1 + PS5: 1 + Stadia: 1 + WebGL: 1 + Windows Store Apps: 1 + XboxOne: 1 + iPhone: 1 + tvOS: 1 incrementalIl2cppBuild: {} suppressCommonWarnings: 1 allowUnsafeCode: 0 useDeterministicCompilation: 1 enableRoslynAnalyzers: 1 - selectedPlatform: 3 additionalIl2CppArgs: scriptingRuntimeVersion: 1 gcIncremental: 0 @@ -807,6 +822,7 @@ PlayerSettings: metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 platformCapabilities: {} metroTargetDeviceFamilies: {} metroFTAName: diff --git a/Runtime/BinaryPrefs.Builder.cs b/Runtime/BinaryPrefs.Builder.cs index 4ba827d..414a45e 100644 --- a/Runtime/BinaryPrefs.Builder.cs +++ b/Runtime/BinaryPrefs.Builder.cs @@ -11,6 +11,11 @@ public partial class BinaryPrefs static partial void ThrowIfFilePathLocked(string filePath); static partial void UnlockFilePathInEditor(string filePath); + /// + /// Creates and configures a new instance of with default settings. + /// + /// The file path for the storage. + /// A configured instance. public static BinaryPrefs Get(string filePath) { return Construct(filePath) @@ -19,6 +24,11 @@ public static BinaryPrefs Get(string filePath) .Build(); } + /// + /// Begins the construction of a new instance. + /// + /// The file path for the storage. + /// A for configuring the instance. public static Builder Construct(string filePath) { ThrowIfFilePathLocked(filePath); @@ -26,6 +36,10 @@ public static Builder Construct(string filePath) return new Builder(filePath); } + /// + /// Deletes the storage file at the specified path. + /// + /// The path to the storage file. internal static void Delete(string storagePath) { if (File.Exists(storagePath)) @@ -34,23 +48,34 @@ internal static void Delete(string storagePath) } } + /// + /// Provides a fluent interface for configuring and building a instance. + /// public class Builder { private readonly string _filePath; private readonly List _serializers = new(); private bool _autoSave; - public Builder(string filePath) + internal Builder(string filePath) { _filePath = filePath; } + /// + /// Enables automatic saving of changes to the storage. + /// + /// The current instance for method chaining. public Builder EnableAutoSaveOnChange() { _autoSave = true; return this; } + /// + /// Adds serializers for primitive types to the storage configuration. + /// + /// The current instance for method chaining. public Builder AddPrimitiveTypes() { return AddTypeSerializer(BooleanSerializer.Shared) @@ -77,6 +102,13 @@ public Builder AddPrimitiveTypes() .AddTypeSerializer(Vector3IntSerializer.Shared); } + /// + /// Adds a serializer for a specified type to the storage configuration. + /// + /// The type to be serialized. + /// The serializer for the specified type. + /// The current instance for method chaining. + /// Thrown if a serializer for the specified type already exists. public Builder AddTypeSerializer(TypeSerializer typeSerializer) { if (_serializers.Any(c => c is TypedBinarySection)) @@ -87,6 +119,13 @@ public Builder AddTypeSerializer(TypeSerializer typeSerializer) return this; } + /// + /// Adds support for a specified enum type to the storage configuration. + /// + /// The enum type to be supported. + /// Whether to use the full name of the enum type. + /// The current instance for method chaining. + /// Thrown if the enum has an unexpected underlying type. public Builder SupportEnum(bool useFullName = false) where T : unmanaged, Enum { @@ -124,38 +163,62 @@ public Builder SupportEnum(bool useFullName = false) return this; } + /// + /// Adds support for lists of a specified type to the storage configuration. + /// + /// The type of elements in the list. + /// The current instance for method chaining. + /// Thrown if the specified type is not supported. public Builder SupportListsOf() { if (_serializers.FirstOrDefault(c => c is TypedBinarySection) is not TypedBinarySection section) { - throw new CantSupportListOfException(typeof(T)); + throw new CantSupportCollectionOfException(typeof(T)); } return AddTypeSerializer(new CollectionTypeSerializer>(section.Serializer)); } + /// + /// Adds support for sets of a specified type to the storage configuration. + /// + /// The type of elements in the set. + /// The current instance for method chaining. + /// Thrown if the specified type is not supported. public Builder SupportSetsOf() { if (_serializers.FirstOrDefault(c => c is TypedBinarySection) is not TypedBinarySection section) { - throw new CantSupportListOfException(typeof(T)); + throw new CantSupportCollectionOfException(typeof(T)); } return AddTypeSerializer(new CollectionTypeSerializer>(section.Serializer)); } + /// + /// Adds support for dictionaries of specified key and value types to the storage configuration. + /// + /// The type of the dictionary keys. + /// The type of the dictionary values. + /// The current instance for method chaining. + /// Thrown if the specified key or value type is not supported. public Builder SupportDictionariesOf() { if (_serializers.FirstOrDefault(c => c is TypedBinarySection) is not TypedBinarySection keySection || _serializers.FirstOrDefault(c => c is TypedBinarySection) is not TypedBinarySection valueSection) { - throw new CantSupportListOfException(typeof(KeyValuePair)); + throw new CantSupportCollectionOfException(typeof(KeyValuePair)); } var kvSerializer = new KeyValueTypeSerializer(keySection.Serializer, valueSection.Serializer); return AddTypeSerializer(new CollectionTypeSerializer, ReactiveDictionary>(kvSerializer)); } + /// + /// Builds and returns the configured instance. + /// + /// The configured instance. + /// Thrown if the storage fails to load data from disk. public BinaryPrefs Build() { try diff --git a/Runtime/BinaryPrefs.cs b/Runtime/BinaryPrefs.cs index d97b40c..fc8f2fb 100644 --- a/Runtime/BinaryPrefs.cs +++ b/Runtime/BinaryPrefs.cs @@ -7,6 +7,9 @@ namespace Appegy.BinaryStorage { + /// + /// Manages a binary storage system for saving, retrieving, and managing records of various types. + /// public partial class BinaryPrefs : IDisposable { private readonly string _storageFilePath; @@ -15,10 +18,26 @@ public partial class BinaryPrefs : IDisposable private readonly Dictionary _data = new(); private int _changeScopeCounter; + /// + /// Gets or sets a value indicating whether data should be saved automatically. + /// public bool AutoSave { get; set; } + + /// + /// Gets a value indicating whether there are unsaved changes. + /// public bool IsDirty { get; private set; } + + /// + /// Gets a value indicating whether the storage has been disposed. + /// public bool IsDisposed { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// The file path for storing data. + /// The list of supported types for storage. internal BinaryPrefs(string storageFilePath, IReadOnlyList supportedTypes) { _storageFilePath = storageFilePath; @@ -27,12 +46,22 @@ internal BinaryPrefs(string storageFilePath, IReadOnlyList suppor #region Public API + /// + /// Determines whether the specified key exists in the storage. + /// + /// The key to check for existence. + /// True if the key exists; otherwise, false. public virtual bool Has(string key) { ThrowIfDisposed(); return _data.ContainsKey(key); } + /// + /// Gets the type of the value associated with the specified key. + /// + /// The key to get the type for. + /// The type of the value associated with the key, or null if the key does not exist. [CanBeNull] public virtual Type TypeOf(string key) { @@ -40,6 +69,11 @@ public virtual Type TypeOf(string key) return _data.TryGetValue(key, out var record) ? record.Type : null; } + /// + /// Determines whether the storage supports the specified type. + /// + /// The type to check for support. + /// True if the type is supported; otherwise, false. public virtual bool Supports() { ThrowIfDisposed(); @@ -47,6 +81,13 @@ public virtual bool Supports() return _supportedTypes.Any(c => c is TypedBinarySection); } + /// + /// Gets the value associated with the specified key. + /// + /// The type of the value. + /// The key to get the value for. + /// The initial value to use if the key does not exist. + /// The value associated with the key. public virtual T Get(string key, T initValue = default) { ThrowIfDisposed(); @@ -59,6 +100,14 @@ public virtual T Get(string key, T initValue = default) return typedRecord.Value; } + /// + /// Sets the value for the specified key. + /// + /// The type of the value. + /// The key to set the value for. + /// The value to set. + /// Whether to override the type if the key already exists. + /// True if the value was set; otherwise, false. public virtual bool Set(string key, T value, bool overrideTypeIfExists = false) { ThrowIfDisposed(); @@ -90,61 +139,22 @@ public virtual bool Set(string key, T value, bool overrideTypeIfExists = fals return true; } - #region Collections - - public virtual bool SupportsListsOf() - { - ThrowIfDisposed(); - return _supportedTypes.Any(c => c is TypedBinarySection>); - } - - public virtual bool SupportsSetsOf() - { - ThrowIfDisposed(); - return _supportedTypes.Any(c => c is TypedBinarySection>); - } - - public virtual bool SupportsDictionariesOf() - { - ThrowIfDisposed(); - return _supportedTypes.Any(c => c is TypedBinarySection>); - } - - public IList GetListOf(string key) - { - return GetCollectionOf>(key); - } - - public ISet GetSetOf(string key) - { - return GetCollectionOf>(key); - } - - public IDictionary GetDictionaryOf(string key) - { - return GetCollectionOf, ReactiveDictionary>(key); - } - - private TCollection GetCollectionOf(string key) - where TCollection : ICollection, IReactiveCollection, new() - { - ThrowIfDisposed(); - var record = GetRecord(key) ?? AddRecord(key, new TCollection()); - if (record is not Record typedRecord) - { - throw new UnexpectedTypeException(key, nameof(Get), record.Type, typeof(IList)); - } - return typedRecord.Value; - } - - #endregion - + /// + /// Removes the value associated with the specified key. + /// + /// The key to remove the value for. + /// True if the key was removed; otherwise, false. public virtual bool Remove(string key) { ThrowIfDisposed(); return RemoveRecord(key); } + /// + /// Removes values based on a predicate. + /// + /// The predicate to determine which keys to remove. + /// The number of keys removed. public virtual int Remove(Func predicate) { ThrowIfDisposed(); @@ -162,6 +172,10 @@ public virtual int Remove(Func predicate) return removed; } + /// + /// Removes all values from the storage. + /// + /// The number of keys removed. public virtual int RemoveAll() { ThrowIfDisposed(); @@ -170,11 +184,18 @@ public virtual int RemoveAll() return count; } + /// + /// Saves the current data to disk. + /// public virtual void Save() { SaveDataFromDisk(); } + /// + /// Begins a scope for making multiple changes. + /// + /// An IDisposable to end the scope. public IDisposable MultipleChangeScope() { ThrowIfDisposed(); @@ -182,49 +203,99 @@ public IDisposable MultipleChangeScope() return new DisposableScope(DecreaseCounter); } - private void SaveDataFromDisk() - { - ThrowIfDisposed(); - BinaryPrefsIO.SaveDataOnDisk(_storageFilePath, _supportedTypes, _data); - IsDirty = false; - } + #region Collections - private void LoadDataFromDisk() + /// + /// Determines whether the storage supports lists of the specified type. + /// + /// The type to check for support. + /// True if lists of the type are supported; otherwise, false. + public virtual bool SupportsListsOf() => SupportsCollectionOf>(); + + /// + /// Determines whether the storage supports sets of the specified type. + /// + /// The type to check for support. + /// True if sets of the type are supported; otherwise, false. + public virtual bool SupportsSetsOf() => SupportsCollectionOf>(); + + /// + /// Determines whether the storage supports dictionaries of the specified key and value types. + /// + /// The type of the dictionary keys. + /// The type of the dictionary values. + /// True if dictionaries of the key and value types are supported; otherwise, false. + public virtual bool SupportsDictionariesOf() => SupportsCollectionOf, ReactiveDictionary>(); + + /// + /// Gets the list associated with the specified key. + /// + /// The type of the list elements. + /// The key to get the list for. + /// The list associated with the key. + public IList GetListOf(string key) => GetCollectionOf>(key); + + /// + /// Gets the set associated with the specified key. + /// + /// The type of the set elements. + /// The key to get the set for. + /// The set associated with the key. + public ISet GetSetOf(string key) => GetCollectionOf>(key); + + /// + /// Gets the dictionary associated with the specified key. + /// + /// The type of the dictionary keys. + /// The type of the dictionary values. + /// The key to get the dictionary for. + /// The dictionary associated with the key. + public IDictionary GetDictionaryOf(string key) => GetCollectionOf, ReactiveDictionary>(key); + + /// + /// Determines whether the specified collection type is supported. + /// + /// The type of elements in the collection. + /// The type of the collection. + /// true if the specified collection type is supported; otherwise, false. + private bool SupportsCollectionOf() where TCollection : IReactiveCollection { ThrowIfDisposed(); - BinaryPrefsIO.LoadDataFromDisk(_storageFilePath, _supportedTypes, _data); - foreach (var rc in _data.Values.Select(c => c.Object).OfType()) - { - rc.OnChanged += MarkChanged; - } + return _supportedTypes.Any(c => c is TypedBinarySection); } - public virtual void Dispose() + /// + /// Gets the collection associated with the specified key. + /// + /// The type of the collection elements. + /// The type of the collection. + /// The key to get the collection for. + /// The collection associated with the key. + private TCollection GetCollectionOf(string key) + where TCollection : ICollection, IReactiveCollection, new() { - if (IsDisposed) + ThrowIfDisposed(); + var record = GetRecord(key) ?? AddRecord(key, new TCollection()); + if (record is not Record typedRecord) { - return; + throw new UnexpectedTypeException(key, nameof(Get), record.Type, typeof(IList)); } - _data.Clear(); - _supportedTypes.ForEach(static c => c.Count = 0); - IsDisposed = true; - UnlockFilePathInEditor(_storageFilePath); + return typedRecord.Value; } #endregion - #region Immutable methods - - [CanBeNull] - private Record GetRecord(string key) - { - return _data.GetValueOrDefault(key); - } - #endregion #region Mutable methods + /// + /// Adds a new record with the specified key and value. + /// + /// The type of the value. + /// The key to add the record for. + /// The value to add. + /// The added record. private Record AddRecord(string key, T value) { var typeIndex = _supportedTypes.FindIndex(static c => c is TypedBinarySection); @@ -245,6 +316,13 @@ private Record AddRecord(string key, T value) return record; } + /// + /// Changes the value of an existing record. + /// + /// The type of the value. + /// The record to change. + /// The new value. + /// True if the value was changed; otherwise, false. private bool ChangeRecord(Record record, T value) { var serializer = ((TypedBinarySection)_supportedTypes[record.TypeIndex]).Serializer; @@ -257,6 +335,11 @@ private bool ChangeRecord(Record record, T value) return !result; } + /// + /// Removes the record associated with the specified key. + /// + /// The key to remove the record for. + /// True if the record was removed; otherwise, false. private bool RemoveRecord(string key) { if (!_data.TryGetValue(key, out var value)) @@ -274,6 +357,9 @@ private bool RemoveRecord(string key) return true; } + /// + /// Removes all records from the storage. + /// private void RemoveAllRecords() { using (MultipleChangeScope()) @@ -294,6 +380,22 @@ private void RemoveAllRecords() #endregion + #region Private methods + + /// + /// Gets the record associated with the specified key. + /// + /// The key to get the record for. + /// The record associated with the key, or null if the key does not exist. + [CanBeNull] + private Record GetRecord(string key) + { + return _data.GetValueOrDefault(key); + } + + /// + /// Decreases the change scope counter and saves data if necessary. + /// private void DecreaseCounter() { if (_changeScopeCounter == 0) @@ -312,6 +414,9 @@ private void DecreaseCounter() } } + /// + /// Marks the storage as changed and saves data if necessary. + /// private void MarkChanged() { if (!AutoSave || _changeScopeCounter > 0) @@ -322,6 +427,10 @@ private void MarkChanged() SaveDataFromDisk(); } + /// + /// Throws an exception if the storage has been disposed. + /// + /// Thrown if the storage is disposed. private void ThrowIfDisposed() { if (IsDisposed) @@ -330,6 +439,11 @@ private void ThrowIfDisposed() } } + /// + /// Throws an exception if the specified type is a collection. + /// + /// The type to check. + /// Thrown if the type is a collection. private void ThrowIfCollection() { var type = typeof(T); @@ -338,5 +452,83 @@ private void ThrowIfCollection() throw new IncorrectUsageOfCollectionException(nameof(Get), type); } } + + #endregion + + #region Dispose Pattern + + /// + /// Finalizer + /// + ~BinaryPrefs() + { + Dispose(false); + } + + /// + /// Disposes the resources used by the storage. + /// + public virtual void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the resources used by the storage. + /// + /// Whether managed resources should be disposed. + private void Dispose(bool disposing) + { + if (IsDisposed) + { + return; + } + + // Always dispose IReactiveCollection instances + foreach (var rc in _data.Values.Select(c => c.Object).OfType()) + { + rc.OnChanged -= MarkChanged; + rc.Dispose(); + } + + if (disposing) + { + _data.Clear(); + _supportedTypes.ForEach(static c => c.Count = 0); + } + + IsDisposed = true; + UnlockFilePathInEditor(_storageFilePath); + } + + #endregion + + #region File System IO + + /// + /// Loads the data from disk into memory. + /// + private void LoadDataFromDisk() + { + ThrowIfDisposed(); + BinaryPrefsIO.LoadDataFromDisk(_storageFilePath, _supportedTypes, _data); + foreach (var rc in _data.Values.Select(c => c.Object).OfType()) + { + rc.OnChanged += MarkChanged; + } + } + + /// + /// Saves the data from memory to disk. + /// + private void SaveDataFromDisk() + { + ThrowIfDisposed(); + BinaryPrefsIO.SaveDataOnDisk(_storageFilePath, _supportedTypes, _data); + IsDirty = false; + } + + #endregion } } \ No newline at end of file diff --git a/Runtime/Exceptions/CantSupportListOfException.cs b/Runtime/Exceptions/CantSupportCollectionOfException.cs similarity index 71% rename from Runtime/Exceptions/CantSupportListOfException.cs rename to Runtime/Exceptions/CantSupportCollectionOfException.cs index 82f5ce3..0d8f8b6 100644 --- a/Runtime/Exceptions/CantSupportListOfException.cs +++ b/Runtime/Exceptions/CantSupportCollectionOfException.cs @@ -2,11 +2,11 @@ namespace Appegy.BinaryStorage { - public class CantSupportListOfException : Exception + public class CantSupportCollectionOfException : Exception { public Type ItemType { get; } - public CantSupportListOfException(Type itemType) + public CantSupportCollectionOfException(Type itemType) : base($"Add serializer for {itemType.FullName} using {nameof(BinaryPrefs.Builder.AddTypeSerializer)} before adding support for List<{itemType.Name}>.") { ItemType = itemType; diff --git a/Runtime/Exceptions/CantSupportListOfException.cs.meta b/Runtime/Exceptions/CantSupportCollectionOfException.cs.meta similarity index 100% rename from Runtime/Exceptions/CantSupportListOfException.cs.meta rename to Runtime/Exceptions/CantSupportCollectionOfException.cs.meta diff --git a/Runtime/Serialization/CollectionTypeSerializer.cs b/Runtime/Serialization/CollectionTypeSerializer.cs index b34edc4..4004fe5 100644 --- a/Runtime/Serialization/CollectionTypeSerializer.cs +++ b/Runtime/Serialization/CollectionTypeSerializer.cs @@ -17,11 +17,6 @@ public CollectionTypeSerializer(TypeSerializer typeSerializer) TypeName = $"{typeof(TCollection).Name}<{typeSerializer.TypeName}>"; } - public override int SizeOf(TCollection value) - { - return sizeof(int) + value.Sum(c => _typeSerializer.SizeOf(c)); - } - public override bool Equals(TCollection value1, TCollection value2) { return value1 == value2; diff --git a/Runtime/Serialization/EnumTypeSerializer.cs b/Runtime/Serialization/EnumTypeSerializer.cs index 578eafe..423615b 100644 --- a/Runtime/Serialization/EnumTypeSerializer.cs +++ b/Runtime/Serialization/EnumTypeSerializer.cs @@ -10,7 +10,6 @@ public class EnumTypeSerializer : TypeSerializer private readonly TypeSerializer _numberType; public override string TypeName { get; } - public override int SizeOf(TEnum value) => _numberType.SizeOf(ToNumber(value)); public override bool Equals(TEnum value1, TEnum value2) { diff --git a/Runtime/Serialization/KeyValueTypeSerializer.cs b/Runtime/Serialization/KeyValueTypeSerializer.cs index 781e260..d2071fa 100644 --- a/Runtime/Serialization/KeyValueTypeSerializer.cs +++ b/Runtime/Serialization/KeyValueTypeSerializer.cs @@ -17,11 +17,6 @@ public KeyValueTypeSerializer(TypeSerializer keySerializer, TypeSerializer TypeName = $"{typeof(TKey).Name}:{typeof(TValue).Name}"; } - public override int SizeOf(KeyValuePair value) - { - return _keySerializer.SizeOf(value.Key) + _valueSerializer.SizeOf(value.Value); - } - public override bool Equals(KeyValuePair value1, KeyValuePair value2) { return _keySerializer.Equals(value1.Key, value2.Key) && _valueSerializer.Equals(value1.Value, value2.Value); diff --git a/Runtime/Serialization/SystemTypesSerializers.cs b/Runtime/Serialization/SystemTypesSerializers.cs index 808fce0..5a51e34 100644 --- a/Runtime/Serialization/SystemTypesSerializers.cs +++ b/Runtime/Serialization/SystemTypesSerializers.cs @@ -9,7 +9,6 @@ internal class BooleanSerializer : EquatableTypeSerializer { public static BooleanSerializer Shared { get; } = new(); public override string TypeName => "bool"; - public override int SizeOf(bool _) => sizeof(bool); public override void WriteTo(BinaryWriter writer, bool value) => writer.Write(value); public override bool ReadFrom(BinaryReader reader) => reader.ReadBoolean(); } @@ -18,7 +17,6 @@ internal class CharSerializer : EquatableTypeSerializer { public static CharSerializer Shared { get; } = new(); public override string TypeName => "char"; - public override int SizeOf(char value) => sizeof(char); public override void WriteTo(BinaryWriter writer, char value) { @@ -49,7 +47,6 @@ internal class ByteSerializer : EquatableTypeSerializer { public static ByteSerializer Shared { get; } = new(); public override string TypeName => "byte"; - public override int SizeOf(byte _) => sizeof(byte); public override void WriteTo(BinaryWriter writer, byte value) => writer.Write(value); public override byte ReadFrom(BinaryReader reader) => reader.ReadByte(); } @@ -58,7 +55,6 @@ internal class SByteSerializer : EquatableTypeSerializer { public static SByteSerializer Shared { get; } = new(); public override string TypeName => "sbyte"; - public override int SizeOf(sbyte _) => sizeof(sbyte); public override void WriteTo(BinaryWriter writer, sbyte value) => writer.Write(value); public override sbyte ReadFrom(BinaryReader reader) => reader.ReadSByte(); } @@ -67,7 +63,6 @@ internal class Int16Serializer : EquatableTypeSerializer { public static Int16Serializer Shared { get; } = new(); public override string TypeName => "short"; - public override int SizeOf(short _) => sizeof(short); public override void WriteTo(BinaryWriter writer, short value) => writer.Write(value); public override short ReadFrom(BinaryReader reader) => reader.ReadInt16(); } @@ -76,7 +71,6 @@ internal class UInt16Serializer : EquatableTypeSerializer { public static UInt16Serializer Shared { get; } = new(); public override string TypeName => "ushort"; - public override int SizeOf(ushort _) => sizeof(short); public override void WriteTo(BinaryWriter writer, ushort value) => writer.Write(value); public override ushort ReadFrom(BinaryReader reader) => reader.ReadUInt16(); } @@ -85,7 +79,6 @@ internal class Int32Serializer : EquatableTypeSerializer { public static Int32Serializer Shared { get; } = new(); public override string TypeName => "int"; - public override int SizeOf(int _) => sizeof(int); public override void WriteTo(BinaryWriter writer, int value) => writer.Write(value); public override int ReadFrom(BinaryReader reader) => reader.ReadInt32(); } @@ -94,7 +87,6 @@ internal class UInt32Serializer : EquatableTypeSerializer { public static UInt32Serializer Shared { get; } = new(); public override string TypeName => "uint"; - public override int SizeOf(uint _) => sizeof(uint); public override void WriteTo(BinaryWriter writer, uint value) => writer.Write(value); public override uint ReadFrom(BinaryReader reader) => reader.ReadUInt32(); } @@ -103,7 +95,6 @@ internal class Int64Serializer : EquatableTypeSerializer { public static Int64Serializer Shared { get; } = new(); public override string TypeName => "long"; - public override int SizeOf(long _) => sizeof(long); public override void WriteTo(BinaryWriter writer, long value) => writer.Write(value); public override long ReadFrom(BinaryReader reader) => reader.ReadInt64(); } @@ -112,7 +103,6 @@ internal class UInt64Serializer : EquatableTypeSerializer { public static UInt64Serializer Shared { get; } = new(); public override string TypeName => "ulong"; - public override int SizeOf(ulong _) => sizeof(ulong); public override void WriteTo(BinaryWriter writer, ulong value) => writer.Write(value); public override ulong ReadFrom(BinaryReader reader) => reader.ReadUInt64(); } @@ -121,7 +111,6 @@ internal class SingleSerializer : EquatableTypeSerializer { public static SingleSerializer Shared { get; } = new(); public override string TypeName => "float"; - public override int SizeOf(float _) => sizeof(float); public override void WriteTo(BinaryWriter writer, float value) => writer.Write(value); public override float ReadFrom(BinaryReader reader) => reader.ReadSingle(); } @@ -130,7 +119,6 @@ internal class DoubleSerializer : EquatableTypeSerializer { public static DoubleSerializer Shared { get; } = new(); public override string TypeName => "double"; - public override int SizeOf(double _) => sizeof(double); public override void WriteTo(BinaryWriter writer, double value) => writer.Write(value); public override double ReadFrom(BinaryReader reader) => reader.ReadDouble(); } @@ -139,7 +127,6 @@ internal class DecimalSerializer : EquatableTypeSerializer { public static DecimalSerializer Shared { get; } = new(); public override string TypeName => "decimal"; - public override int SizeOf(decimal _) => sizeof(decimal); public override void WriteTo(BinaryWriter writer, decimal value) => writer.Write(value); public override decimal ReadFrom(BinaryReader reader) => reader.ReadDecimal(); } @@ -150,7 +137,6 @@ internal class StringSerializer : EquatableTypeSerializerRef public static readonly Encoding Encoding = Encoding.UTF8; public override string TypeName => "string"; - public override int SizeOf(string value) => sizeof(int) + (value != null ? Encoding.GetByteCount(value) : 0); public override void WriteTo(BinaryWriter writer, string value) { @@ -164,7 +150,7 @@ public override void WriteTo(BinaryWriter writer, string value) } else { - var size = SizeOf(value); + var size = sizeof(int) + Encoding.GetByteCount(value); var buffer = ArrayPool.Shared.Rent(size); var bufferSize = Encoding.GetBytes(value, buffer); writer.Write(bufferSize); @@ -200,24 +186,16 @@ public override string ReadFrom(BinaryReader reader) internal class DateTimeSerializer : EquatableTypeSerializer { public static DateTimeSerializer Shared { get; } = new(); - public override string TypeName => "DateTime"; - public override int SizeOf(DateTime _) => sizeof(long); - public override void WriteTo(BinaryWriter writer, DateTime value) => writer.Write(value.ToBinary()); - public override DateTime ReadFrom(BinaryReader reader) => DateTime.FromBinary(reader.ReadInt64()); } internal class TimeSpanSerializer : EquatableTypeSerializer { public static TimeSpanSerializer Shared { get; } = new(); - public override string TypeName => "TimeSpan"; - public override int SizeOf(TimeSpan _) => sizeof(long); - public override void WriteTo(BinaryWriter writer, TimeSpan value) => writer.Write(value.Ticks); - public override TimeSpan ReadFrom(BinaryReader reader) => TimeSpan.FromTicks(reader.ReadInt64()); } } \ No newline at end of file diff --git a/Runtime/Serialization/TypeSerializer.cs b/Runtime/Serialization/TypeSerializer.cs index 915faf4..ea4d05a 100644 --- a/Runtime/Serialization/TypeSerializer.cs +++ b/Runtime/Serialization/TypeSerializer.cs @@ -11,7 +11,6 @@ public abstract class TypeSerializer public abstract class TypeSerializer : TypeSerializer { public override string TypeName { get; } = typeof(T).FullName; - public abstract int SizeOf(T value); public abstract bool Equals(T value1, T value2); public abstract void WriteTo(BinaryWriter writer, T value); public abstract T ReadFrom(BinaryReader reader); diff --git a/Runtime/Serialization/UnityTypesSerializers.cs b/Runtime/Serialization/UnityTypesSerializers.cs index 5558830..c8e5d10 100644 --- a/Runtime/Serialization/UnityTypesSerializers.cs +++ b/Runtime/Serialization/UnityTypesSerializers.cs @@ -7,7 +7,6 @@ internal class QuaternionSerializer : EquatableTypeSerializer { public static QuaternionSerializer Shared { get; } = new(); public override string TypeName => "vector2f"; - public override int SizeOf(Quaternion _) => sizeof(float) * 4; public override void WriteTo(BinaryWriter writer, Quaternion value) { @@ -31,7 +30,6 @@ internal class Vector2Serializer : EquatableTypeSerializer { public static Vector2Serializer Shared { get; } = new(); public override string TypeName => "vector2f"; - public override int SizeOf(Vector2 _) => sizeof(float) * 2; public override void WriteTo(BinaryWriter writer, Vector2 value) { @@ -51,7 +49,6 @@ internal class Vector3Serializer : EquatableTypeSerializer { public static Vector3Serializer Shared { get; } = new(); public override string TypeName => "vector3f"; - public override int SizeOf(Vector3 _) => sizeof(float) * 3; public override void WriteTo(BinaryWriter writer, Vector3 value) { @@ -73,7 +70,6 @@ internal class Vector4Serializer : EquatableTypeSerializer { public static Vector4Serializer Shared { get; } = new(); public override string TypeName => "vector4f"; - public override int SizeOf(Vector4 _) => sizeof(float) * 4; public override void WriteTo(BinaryWriter writer, Vector4 value) { @@ -97,7 +93,6 @@ internal class Vector2IntSerializer : EquatableTypeSerializer { public static Vector2IntSerializer Shared { get; } = new(); public override string TypeName => "vector2i"; - public override int SizeOf(Vector2Int _) => sizeof(int) * 2; public override void WriteTo(BinaryWriter writer, Vector2Int value) { @@ -117,7 +112,6 @@ internal class Vector3IntSerializer : EquatableTypeSerializer { public static Vector3IntSerializer Shared { get; } = new(); public override string TypeName => "vector3i"; - public override int SizeOf(Vector3Int _) => sizeof(int) * 3; public override void WriteTo(BinaryWriter writer, Vector3Int value) { diff --git a/Tests/TypeSerializers/BaseTypeSerializerTests.cs b/Tests/TypeSerializers/BaseTypeSerializerTests.cs index 4847bcf..2c64c12 100644 --- a/Tests/TypeSerializers/BaseTypeSerializerTests.cs +++ b/Tests/TypeSerializers/BaseTypeSerializerTests.cs @@ -30,7 +30,6 @@ public TypeSerializerTests(TType defaultValue, TTypeSerializer serializer) public void General_Checks_For_Type_Serializer() { // Arrange - var size = _serializer.SizeOf(_defaultValue); using var writeStream = new MemoryStream(_buffer); using var readStream = new MemoryStream(_buffer); using var writer = new BinaryWriter(writeStream); @@ -41,8 +40,7 @@ public void General_Checks_For_Type_Serializer() var readValue = _serializer.ReadFrom(reader); // Assert - writeStream.Position.Should().Be(size, "Write stream position should match the size of the serialized value"); - readStream.Position.Should().Be(size, "Read stream position should match the size of the serialized value"); + writeStream.Position.Should().Be(readStream.Position, "Write stream position should match the read stream position"); readValue.Should().Be(_defaultValue, "The deserialized value should be equal to the default value"); _serializer.Equals(_defaultValue, _defaultValue).Should().Be(true, "The default value should be equal to itself using serializer's Equals"); _serializer.Equals(_defaultValue, readValue).Should().Be(true, "The default value should be equal to the deserialized value using serializer's Equals");