diff --git a/Packages/com.chark.game-management/CHANGELOG.md b/Packages/com.chark.game-management/CHANGELOG.md
index 8a8f071..8baa2e5 100644
--- a/Packages/com.chark.game-management/CHANGELOG.md
+++ b/Packages/com.chark.game-management/CHANGELOG.md
@@ -9,19 +9,22 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Added
-- `ISerializer` with `GameManager.TryDeserializeValue` and `GameManager.TrySerializeValue` methods, this can be used to serialize and deserialize data using internal `GameManager` serialization utilities.
-- `GameManager.DeleteRuntimeValueAsync` which can be used to delete data asynchronously.
-- `GameManager.GetResourceAsync` which can be used to retrieve resources from StreamingAssets directory.
+- `GameManager.TryDeserializeValue`, `GameManager.TrySerializeValue`, `GameManager.TryDeserializeStream` and `GameManager.TrySerializeStream` methods, these can be used to serialize and deserialize data using internal `GameManager` serialization utilities. You can customize this via `ISerializer` interface (see `CreateSerializer` method on `GameManager`).
+- `GameManager.DeleteDataAsync` which can be used to delete data asynchronously.
+- `GameManager.ReadResourceAsync` and `GameManager.ReadResourceStreamAsync` methods which can be used to retrieve resources from StreamingAssets directory.
+- `GameManager.ReadDataStream` and `GameManager.ReadDataStreamAsync` methods which can be used to read a `Stream` from a file on disk.
+- `GameManager.SaveDataStream` and `GameManager.SaveDataStreamAsync` methods which can be used to persist a `Stream` to disk.
### Changed
-- Renamed `IResourceLoader` methods to use `Get*` prefix instead of `Load*` so its more consistent with other methods.
+- Renamed some `IResourceLoader` methods to use `Get*` prefix instead of `Load*` so its more consistent with other methods. Methods which read from _StreamingAssets_ directory will use `Read*` prefix.
- Renamed `IGameStorage` to `IStorage`.
-- Cancellation tokens can now be used in async methods.
+- Cancellation tokens can now be used in all async methods.
+- Renamed `IStorage` methods to use `Read*` and `Save*` prefixes to emphasise that these methods interact with data on dist.
### Fixed
-- `GameStorage.GetValueAsync` not switching back to main thread when no value is found.
+- `GameStorage.GetValueAsync` (now `GameStorage.ReadValueAsync`) not switching back to main thread when no value is found.
## [v0.0.2](https://github.com/chark/game-management/compare/v0.0.1...v0.0.2) - 2023-10-06
diff --git a/Packages/com.chark.game-management/Runtime/Assets/IResourceLoader.cs b/Packages/com.chark.game-management/Runtime/Assets/IResourceLoader.cs
index 7e74274..93df01e 100644
--- a/Packages/com.chark.game-management/Runtime/Assets/IResourceLoader.cs
+++ b/Packages/com.chark.game-management/Runtime/Assets/IResourceLoader.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
@@ -8,24 +9,46 @@ namespace CHARK.GameManagement.Assets
public interface IResourceLoader
{
///
- /// Enumerable of resources retrieved from given .
+ /// Resources of type retrieved from given .
///
+ ///
+ /// is relative to Resources directory.
+ ///
public IEnumerable GetResources(string path = null)
where TResource : Object;
///
- /// true if is retrieved from given
+ /// true if resource of type is retrieved from given
/// or false otherwise.
///
+ ///
+ /// is relative to Resources directory.
+ ///
public bool TryGetResource(string path, out TResource resource)
where TResource : Object;
///
- /// Asset loaded asynchronously from given .
- ///
- /// Note , is relative to StreamingAssets directory.
+ /// Resource of type retrieved from given .
///
- public Task GetResourceAsync(
+ ///
+ /// is relative to StreamingAssets directory.
+ ///
+ ///
+ /// if could not be retrieved.
+ ///
+ public Task ReadResourceAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Resource retrieved from given . If something
+ /// fails, an empty stream is be returned.
+ ///
+ ///
+ /// is relative to StreamingAssets directory.
+ ///
+ public Task ReadResourceStreamAsync(
string path,
CancellationToken cancellationToken = default
);
diff --git a/Packages/com.chark.game-management/Runtime/Assets/ResourceLoader.cs b/Packages/com.chark.game-management/Runtime/Assets/ResourceLoader.cs
index c099220..d9521ea 100644
--- a/Packages/com.chark.game-management/Runtime/Assets/ResourceLoader.cs
+++ b/Packages/com.chark.game-management/Runtime/Assets/ResourceLoader.cs
@@ -1,8 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CHARK.GameManagement.Serialization;
+using CHARK.GameManagement.Utilities;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -46,11 +48,16 @@ public bool TryGetResource(string path, out TResource resource) where
return false;
}
- public async Task GetResourceAsync(
+ public async Task ReadResourceAsync(
string path,
CancellationToken cancellationToken
)
{
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentException($"{path} cannot be null or empty");
+ }
+
var actualPath = Path.Combine(Application.streamingAssetsPath, path);
#if UNITY_ANDROID
@@ -73,7 +80,51 @@ await Cysharp.Threading.Tasks.UnityAsyncExtensions.ToUniTask(
return value;
}
- return default;
+ throw new Exception($"Could not retrieve resource from path: {actualPath}");
+ }
+
+#pragma warning disable CS1998
+ public async Task ReadResourceStreamAsync(
+#pragma warning restore CS1998
+ string path,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ return Stream.Null;
+ }
+
+ var actualPath = Path.Combine(Application.streamingAssetsPath, path);
+
+ try
+ {
+#if UNITY_ANDROID
+ var request = UnityEngine.Networking.UnityWebRequest.Get(actualPath);
+ var operation = request.SendWebRequest();
+
+ await Cysharp.Threading.Tasks.UnityAsyncExtensions.ToUniTask(
+ operation,
+ cancellationToken: cancellationToken
+ );
+
+ var handler = request.downloadHandler;
+ var data = handler.data;
+ if (data == null || data.Length == 0)
+ {
+ return Stream.Null;
+ }
+
+ return new MemoryStream(data);
+#else
+ return File.OpenRead(actualPath);
+#endif
+ }
+ catch (Exception exception)
+ {
+ Logging.LogException(exception, GetType());
+ return Stream.Null;
+ }
}
}
}
diff --git a/Packages/com.chark.game-management/Runtime/Entities/IEntityManager.cs b/Packages/com.chark.game-management/Runtime/Entities/IEntityManager.cs
index 0d1a233..3ae882e 100644
--- a/Packages/com.chark.game-management/Runtime/Entities/IEntityManager.cs
+++ b/Packages/com.chark.game-management/Runtime/Entities/IEntityManager.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace CHARK.GameManagement.Entities
{
@@ -10,7 +11,7 @@ public interface IEntityManager
public IReadOnlyList Entities { get; }
///
- /// Add a to the Entity Manager.
+ /// Add a of type to the Entity Manager.
///
///
/// true if given was added or false otherwise.
@@ -18,7 +19,7 @@ public interface IEntityManager
public bool AddEntity(TEntity entity) where TEntity : class;
///
- /// Remove a from the Entity Manager.
+ /// Remove an if type from the Entity Manager.
///
///
/// true if given was removed or false otherwise.
@@ -26,7 +27,8 @@ public interface IEntityManager
public bool RemoveEntity(TEntity entity) where TEntity : class;
///
- /// true if is retrieved or false otherwise.
+ /// true if of type is retrieved or
+ /// false otherwise.
///
public bool TryGetEntity(out TEntity entity);
@@ -36,9 +38,11 @@ public interface IEntityManager
public IEnumerable GetEntities();
///
- /// Entity of type or throws if entity
- /// is not added to the manager.
+ /// Entity of type .
///
+ ///
+ /// if system of type is not found.
+ ///
public TEntity GetEntity();
}
}
diff --git a/Packages/com.chark.game-management/Runtime/GameManager.External.cs b/Packages/com.chark.game-management/Runtime/GameManager.External.cs
index b4af54f..2ed1b4b 100644
--- a/Packages/com.chark.game-management/Runtime/GameManager.External.cs
+++ b/Packages/com.chark.game-management/Runtime/GameManager.External.cs
@@ -1,7 +1,12 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Threading;
using System.Threading.Tasks;
+using CHARK.GameManagement.Assets;
using CHARK.GameManagement.Messaging;
+using CHARK.GameManagement.Serialization;
+using CHARK.GameManagement.Storage;
using CHARK.GameManagement.Systems;
using Object = UnityEngine.Object;
@@ -9,11 +14,9 @@ namespace CHARK.GameManagement
{
public abstract partial class GameManager
{
- ///
- /// Resources of type from at
- /// given .
- ///
- public static IEnumerable GetResources(string path = null) where TResource : Object
+ ///
+ public static IEnumerable GetResources(string path = null)
+ where TResource : Object
{
var gameManager = GetGameManager();
var resourceLoader = gameManager.resourceLoader;
@@ -21,11 +24,9 @@ public static IEnumerable GetResources(string path = null)
return resourceLoader.GetResources(path);
}
- ///
- /// true if resource of type is retrieved from
- /// at given or false otherwise.
- ///
- public static bool TryGetResource(string path, out TResource resource) where TResource : Object
+ ///
+ public static bool TryGetResource(string path, out TResource resource)
+ where TResource : Object
{
var gameManager = GetGameManager();
var resourceLoader = gameManager.resourceLoader;
@@ -33,115 +34,162 @@ public static bool TryGetResource(string path, out TResource resource
return resourceLoader.TryGetResource(path, out resource);
}
- ///
- /// Task containing a resource of type retrieved from
- /// at given or default if
- /// value could not be retrieved.
- ///
- public static Task GetResourceAsync(string path)
+ ///
+ public static Task ReadResourceAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ )
{
var gameManager = GetGameManager();
var resourceLoader = gameManager.resourceLoader;
- return resourceLoader.GetResourceAsync(path);
+ return resourceLoader.ReadResourceAsync(path, cancellationToken);
}
- ///
- /// Value retrieved from at given
- /// asynchronously or default if no value is could be retrieved.
- ///
- public static Task GetRuntimeValueAsync(string path)
+ ///
+ public static Task ReadResourceStreamAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var gameManager = GetGameManager();
+ var resourceLoader = gameManager.resourceLoader;
+
+ return resourceLoader.ReadResourceStreamAsync(path, cancellationToken);
+ }
+
+ ///
+ public static bool TryReadData(string path, out TData data)
{
var gameManager = GetGameManager();
var runtimeStorage = gameManager.runtimeStorage;
- return runtimeStorage.GetValueAsync(path);
+ return runtimeStorage.TryReadData(path, out data);
}
- ///
- /// true if is retrieved by given
- /// from .
- ///
- public static bool TryGetRuntimeValue(string path, out TValue value)
+ ///
+ public static Task ReadDataAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ )
{
var gameManager = GetGameManager();
var runtimeStorage = gameManager.runtimeStorage;
- return runtimeStorage.TryGetValue(path, out value);
+ return runtimeStorage.ReadDataAsync(path, cancellationToken);
}
- ///
- /// Persist a by given asynchronously to
- /// .
- ///
- public static Task SetRuntimeValueAsync(string path, TValue value)
+ ///
+ public static Stream ReadDataStream(string path)
{
var gameManager = GetGameManager();
var runtimeStorage = gameManager.runtimeStorage;
- return runtimeStorage.SetValueAsync(path, value);
+ return runtimeStorage.ReadDataStream(path);
}
- ///
- /// Persist a by given to
- /// .
- ///
- public static void SetRuntimeValue(string path, TValue value)
+ ///
+ public static Task ReadDataStreamAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ )
{
var gameManager = GetGameManager();
var runtimeStorage = gameManager.runtimeStorage;
- runtimeStorage.SetValue(path, value);
+ return runtimeStorage.ReadDataStreamAsync(path, cancellationToken);
}
- ///
- /// Delete persisted value at given
- /// asynchronously from .
- ///
- public static Task DeleteRuntimeValueAsync(string path)
+ ///
+ public static void SaveData(string path, TData data)
{
var gameManager = GetGameManager();
var runtimeStorage = gameManager.runtimeStorage;
- return runtimeStorage.DeleteValueAsync(path);
+ runtimeStorage.SaveData(path, data);
}
- ///
- /// Delete persisted value at given
- /// from .
- ///
- public static void DeleteRuntimeValue(string path)
+ ///
+ public static void SaveDataStream(string path, Stream stream)
{
var gameManager = GetGameManager();
var runtimeStorage = gameManager.runtimeStorage;
- runtimeStorage.DeleteValue(path);
+ runtimeStorage.SaveDataStream(path, stream);
}
- ///
- /// true if is retrieved by given
- /// from .
- ///
- public static bool TryGetEditorValue(string path, out TValue value)
+ ///
+ public static Task SaveDataAsync(
+ string path,
+ TData data,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var gameManager = GetGameManager();
+ var runtimeStorage = gameManager.runtimeStorage;
+
+ return runtimeStorage.SaveDataAsync(path, data, cancellationToken);
+ }
+
+ ///
+ public static Task SaveDataStreamAsync(
+ string path,
+ Stream stream,
+ CancellationToken cancellationToken = default
+ )
{
- return EditorStorage.TryGetValue(path, out value);
+ var gameManager = GetGameManager();
+ var runtimeStorage = gameManager.runtimeStorage;
+
+ return runtimeStorage.SaveDataStreamAsync(path, stream, cancellationToken);
}
- ///
- /// Persist a by given to
- /// .
- ///
- public static void SetEditorValue(string path, TValue value)
+ ///
+ public static void DeleteData(string path)
{
- EditorStorage.SetValue(path, value);
+ var gameManager = GetGameManager();
+ var runtimeStorage = gameManager.runtimeStorage;
+
+ runtimeStorage.DeleteData(path);
}
- ///
- /// Delete persisted value at given .
- ///
- public static void DeleteEditorValue(string path)
+ ///
+ public static Task DeleteDataAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ )
{
- EditorStorage.DeleteValue(path);
+ var gameManager = GetGameManager();
+ var runtimeStorage = gameManager.runtimeStorage;
+
+ return runtimeStorage.DeleteDataAsync(path, cancellationToken);
+ }
+
+ ///
+ ///
+ /// This method should only be used in Editor, it will not function in builds.
+ ///
+ public static bool TryReadEditorData(string path, out TData data)
+ {
+ return EditorStorage.TryReadData(path, out data);
+ }
+
+ ///
+ ///
+ /// This method should only be used in Editor, it will not function in builds.
+ ///
+ public static void SaveEditorData(string path, TData data)
+ {
+ EditorStorage.SaveData(path, data);
+ }
+
+ ///
+ ///
+ /// This method should only be used in Editor, it will not function in builds.
+ ///
+ public static void DeleteEditorData(string path)
+ {
+ EditorStorage.DeleteData(path);
}
///
@@ -157,7 +205,8 @@ public static bool TryGetSystem(out TSystem system) where TSystem : ISy
}
///
- /// Enumerable of systems of type from .
+ /// Enumerable of systems of type retrieved from
+ /// .
///
public static IEnumerable GetSystems() where TSystem : ISystem
{
@@ -169,8 +218,11 @@ public static IEnumerable GetSystems() where TSystem : ISystem
}
///
- /// Systems of type from .
+ /// System of type retrieved from .
///
+ ///
+ /// if system of type is not found.
+ ///
public static TSystem GetSystem() where TSystem : ISystem
{
var gameManager = GetGameManager();
@@ -179,9 +231,7 @@ public static TSystem GetSystem() where TSystem : ISystem
return entityManager.GetEntity();
}
- ///
- /// Publish a message to the .
- ///
+ ///
public static void Publish(TMessage message) where TMessage : IMessage
{
var gameManager = GetGameManager();
@@ -190,9 +240,7 @@ public static void Publish(TMessage message) where TMessage : IMessage
messageBus.Publish(message);
}
- ///
- /// Add a listener to the .
- ///
+ ///
public static void AddListener(Action listener)
where TMessage : IMessage
{
@@ -202,9 +250,7 @@ public static void AddListener(Action listener)
messageBus.AddListener(listener);
}
- ///
- /// Remove a listener from the .
- ///
+ ///
public static void RemoveListener(Action listener)
where TMessage : IMessage
{
@@ -214,30 +260,22 @@ public static void RemoveListener(Action listener)
messageBus.RemoveListener(listener);
}
- ///
- /// true if is deserialized to
- /// using successfully or
- /// false otherwise.
- ///
- public static bool TryDeserializeValue(string serializedValue, out TValue deserializedValue)
+ ///
+ public static bool TryDeserializeValue(string value, out TValue deserializedValue)
{
var gameManager = GetGameManager();
var serializer = gameManager.serializer;
- return serializer.TryDeserializeValue(serializedValue, out deserializedValue);
+ return serializer.TryDeserializeValue(value, out deserializedValue);
}
- ///
- /// true if is serialized to
- /// using successfully or
- /// false otherwise.
- ///
- public static bool TrySerializeValue(TValue deserializedValue, out string serializedValue)
+ ///
+ public static bool TrySerializeValue(TValue value, out string serializedValue)
{
var gameManager = GetGameManager();
var serializer = gameManager.serializer;
- return serializer.TrySerializeValue(deserializedValue, out serializedValue);
+ return serializer.TrySerializeValue(value, out serializedValue);
}
}
}
diff --git a/Packages/com.chark.game-management/Runtime/GameManager.cs b/Packages/com.chark.game-management/Runtime/GameManager.cs
index 3b50cca..1d17b6f 100644
--- a/Packages/com.chark.game-management/Runtime/GameManager.cs
+++ b/Packages/com.chark.game-management/Runtime/GameManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using CHARK.GameManagement.Assets;
using CHARK.GameManagement.Entities;
using CHARK.GameManagement.Messaging;
@@ -20,7 +21,7 @@ public abstract partial class GameManager : MonoBehaviour
#if UNITY_EDITOR
new EditorPrefsStorage(DefaultSerializer.Instance, $"{nameof(GameManager)}.");
#else
- DefaultStorage.Instance;
+ NullStorage.Instance;
#endif
private static GameManagerSettings Settings => GameManagerSettings.Instance;
@@ -158,11 +159,11 @@ protected virtual ISerializer CreateSerializer()
///
protected virtual IStorage CreateRuntimeStorage()
{
- var keyPrefix = $"{GetGameManagerName()}.";
return new FileStorage(
serializer: serializer,
+ profile: Settings.ActiveProfile,
persistentDataPath: Application.persistentDataPath,
- pathPrefix: keyPrefix
+ pathPrefix: "Data" + Path.DirectorySeparatorChar
);
}
diff --git a/Packages/com.chark.game-management/Runtime/Messaging/IMessageBus.cs b/Packages/com.chark.game-management/Runtime/Messaging/IMessageBus.cs
index ec37e91..2439333 100644
--- a/Packages/com.chark.game-management/Runtime/Messaging/IMessageBus.cs
+++ b/Packages/com.chark.game-management/Runtime/Messaging/IMessageBus.cs
@@ -8,18 +8,19 @@ namespace CHARK.GameManagement.Messaging
public interface IMessageBus
{
///
- /// Publish a and invoke listeners which are listening for
+ /// Publish a and invoke all listeners which are listening for
/// messages of type .
///
public void Publish(TMessage message) where TMessage : IMessage;
///
- /// Add a new for messages of type .
+ /// Add a new which listens for incoming messages of type
+ /// .
///
public void AddListener(Action listener) where TMessage : IMessage;
///
- /// Remove existing from messages of type .
+ /// Remove existing of type .
///
public void RemoveListener(Action listener) where TMessage : IMessage;
}
diff --git a/Packages/com.chark.game-management/Runtime/Messaging/MessageListener.cs b/Packages/com.chark.game-management/Runtime/Messaging/MessageListener.cs
index 460bb91..931365c 100644
--- a/Packages/com.chark.game-management/Runtime/Messaging/MessageListener.cs
+++ b/Packages/com.chark.game-management/Runtime/Messaging/MessageListener.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using UnityEngine;
+using CHARK.GameManagement.Utilities;
namespace CHARK.GameManagement.Messaging
{
@@ -22,7 +22,7 @@ public void Raise(TMessage message)
}
catch (Exception exception)
{
- Debug.LogException(exception);
+ Logging.LogException(exception, GetType());
}
}
}
diff --git a/Packages/com.chark.game-management/Runtime/Serialization/DefaultSerializer.cs b/Packages/com.chark.game-management/Runtime/Serialization/DefaultSerializer.cs
index c502ed4..1f9cf72 100644
--- a/Packages/com.chark.game-management/Runtime/Serialization/DefaultSerializer.cs
+++ b/Packages/com.chark.game-management/Runtime/Serialization/DefaultSerializer.cs
@@ -1,8 +1,10 @@
using System;
using System.ComponentModel;
+using System.IO;
+using System.Text;
+using CHARK.GameManagement.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
-using UnityEngine;
namespace CHARK.GameManagement.Serialization
{
@@ -10,16 +12,18 @@ internal class DefaultSerializer : ISerializer
{
internal static DefaultSerializer Instance { get; } = new();
- private readonly JsonSerializerSettings serializerSettings;
+ private readonly JsonSerializer jsonSerializer = new()
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver(),
+ };
private DefaultSerializer()
{
- serializerSettings = CreateDefaultSerializerSettings();
}
- public bool TryDeserializeValue(string serializedValue, out TValue deserializedValue)
+ public bool TryDeserializeValue(string value, out TValue deserializedValue)
{
- if (TryDeserializeConverterValue(serializedValue, out TValue converterValue))
+ if (TryDeserializeConverterValue(value, out TValue converterValue))
{
deserializedValue = converterValue;
return true;
@@ -29,7 +33,7 @@ public bool TryDeserializeValue(string serializedValue, out TValue deser
try
{
- var jsonValue = JsonConvert.DeserializeObject(serializedValue);
+ var jsonValue = JsonConvert.DeserializeObject(value);
if (jsonValue == null)
{
return false;
@@ -40,42 +44,104 @@ public bool TryDeserializeValue(string serializedValue, out TValue deser
}
catch (Exception exception)
{
- Debug.LogException(exception);
+ Logging.LogException(exception, GetType());
return false;
}
}
- public bool TrySerializeValue(TValue deserializedValue, out string serializedJson)
+ public bool TryDeserializeStream(Stream stream, out TValue deserializedValue)
{
- serializedJson = default;
+ if (TryDeserializeConverterValue(stream, out TValue converterValue))
+ {
+ deserializedValue = converterValue;
+ return true;
+ }
+
+ deserializedValue = default;
+
+ try
+ {
+ using var streamReader = new StreamReader(stream);
+ using var jsonReader = new JsonTextReader(streamReader);
+
+ var jsonValue = jsonSerializer.Deserialize(jsonReader);
+ if (jsonValue == null)
+ {
+ return false;
+ }
+
+ deserializedValue = jsonValue;
+ return true;
+ }
+ catch (Exception exception)
+ {
+ Logging.LogException(exception, GetType());
+ return false;
+ }
+ }
+
+ public bool TrySerializeValue(TValue value, out string serializedValue)
+ {
+ serializedValue = default;
var valueType = typeof(TValue);
if (valueType.IsPrimitive || valueType == typeof(string))
{
- var valueString = deserializedValue.ToString();
- serializedJson = valueString;
+ var valueString = value.ToString();
+ serializedValue = valueString;
return true;
}
try
{
- var valueJson = JsonConvert.SerializeObject(deserializedValue, serializerSettings);
+ using var stringWriter = new StringWriter();
+ jsonSerializer.Serialize(stringWriter, value);
+
+ var valueJson = stringWriter.ToString();
if (string.IsNullOrWhiteSpace(valueJson))
{
return false;
}
- serializedJson = valueJson;
+ serializedValue = valueJson;
return true;
}
catch (Exception exception)
{
- Debug.LogException(exception);
- throw;
+ Logging.LogException(exception, GetType());
+ return false;
}
}
- private static bool TryDeserializeConverterValue(string json, out TValue deserializedValue)
+ public bool TrySerializeStream(Stream stream, out string serializedValue)
+ {
+ serializedValue = default;
+
+ try
+ {
+ using var memoryStream = new MemoryStream();
+ using var streamWriter = new StreamWriter(memoryStream);
+ using var jsonWriter = new JsonTextWriter(streamWriter);
+
+ jsonSerializer.Serialize(jsonWriter, stream);
+
+ var valueJson = Encoding.UTF8.GetString(memoryStream.ToArray());
+ if (string.IsNullOrWhiteSpace(valueJson))
+ {
+ return false;
+ }
+
+ serializedValue = valueJson;
+ return true;
+ }
+ catch (Exception exception)
+ {
+ Logging.LogException(exception, GetType());
+ return false;
+ }
+ }
+
+ private static bool TryDeserializeConverterValue(Stream stream, out TValue deserializedValue)
{
deserializedValue = default;
@@ -88,6 +154,9 @@ private static bool TryDeserializeConverterValue(string json, out TValue
try
{
var converter = TypeDescriptor.GetConverter(valueType);
+ var streamReader = new StreamReader(stream);
+ var json = streamReader.ReadToEnd();
+
var converterValue = (TValue)converter.ConvertFrom(json);
if (converterValue == null)
{
@@ -99,17 +168,38 @@ private static bool TryDeserializeConverterValue(string json, out TValue
}
catch (Exception exception)
{
- Debug.LogException(exception);
+ Logging.LogException(exception, typeof(DefaultSerializer));
return false;
}
}
- private static JsonSerializerSettings CreateDefaultSerializerSettings()
+ private static bool TryDeserializeConverterValue(string json, out TValue deserializedValue)
{
- return new JsonSerializerSettings
+ deserializedValue = default;
+
+ var valueType = typeof(TValue);
+ if (valueType.IsPrimitive == false && valueType != typeof(string))
+ {
+ return false;
+ }
+
+ try
+ {
+ var converter = TypeDescriptor.GetConverter(valueType);
+ var converterValue = (TValue)converter.ConvertFrom(json);
+ if (converterValue == null)
+ {
+ return false;
+ }
+
+ deserializedValue = converterValue;
+ return true;
+ }
+ catch (Exception exception)
{
- ContractResolver = new CamelCasePropertyNamesContractResolver(),
- };
+ Logging.LogException(exception, typeof(DefaultSerializer));
+ return false;
+ }
}
}
}
diff --git a/Packages/com.chark.game-management/Runtime/Serialization/ISerializer.cs b/Packages/com.chark.game-management/Runtime/Serialization/ISerializer.cs
index 6a9c811..13b77aa 100644
--- a/Packages/com.chark.game-management/Runtime/Serialization/ISerializer.cs
+++ b/Packages/com.chark.game-management/Runtime/Serialization/ISerializer.cs
@@ -1,17 +1,31 @@
-namespace CHARK.GameManagement.Serialization
+using System.IO;
+
+namespace CHARK.GameManagement.Serialization
{
public interface ISerializer
{
///
- /// true if is retrieved from
- /// or false otherwise.
+ /// true if is deserialized to
+ /// or false otherwise.
///
- public bool TryDeserializeValue(string serializedValue, out TValue deserializedValue);
+ public bool TryDeserializeValue(string value, out TValue deserializedValue);
///
- /// true if is retrieved from
+ /// true if is deserialized to
/// or false otherwise.
///
- public bool TrySerializeValue(TValue deserializedValue, out string serializedValue);
+ public bool TryDeserializeStream(Stream stream, out TValue deserializedValue);
+
+ ///
+ /// true if is serialized to
+ /// or false otherwise.
+ ///
+ public bool TrySerializeValue(TValue value, out string serializedValue);
+
+ ///
+ /// true if is serialized to
+ /// or false otherwise.
+ ///
+ public bool TrySerializeStream(Stream stream, out string serializedValue);
}
}
diff --git a/Packages/com.chark.game-management/Runtime/Storage/DefaultStorage.cs b/Packages/com.chark.game-management/Runtime/Storage/DefaultStorage.cs
deleted file mode 100644
index 2d312d6..0000000
--- a/Packages/com.chark.game-management/Runtime/Storage/DefaultStorage.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using CHARK.GameManagement.Serialization;
-
-namespace CHARK.GameManagement.Storage
-{
- ///
- /// Game storage which does nothing (placeholder).
- ///
- internal sealed class DefaultStorage : Storage
- {
- public static DefaultStorage Instance { get; } = new();
-
- private DefaultStorage() : base(DefaultSerializer.Instance, default)
- {
- }
-
- public override bool TryGetValue(string path, out TValue value)
- {
- value = default;
- return false;
- }
-
- public override void SetValue(string path, TValue value)
- {
- }
-
- protected override string GetString(string path)
- {
- return default;
- }
-
- protected override void SetString(string path, string value)
- {
- }
-
- protected override void Delete(string path)
- {
- }
- }
-}
diff --git a/Packages/com.chark.game-management/Runtime/Storage/EditorPrefsStorage.cs b/Packages/com.chark.game-management/Runtime/Storage/EditorPrefsStorage.cs
index 4a8b003..bed07e0 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/EditorPrefsStorage.cs
+++ b/Packages/com.chark.game-management/Runtime/Storage/EditorPrefsStorage.cs
@@ -1,4 +1,6 @@
-using CHARK.GameManagement.Serialization;
+using System.IO;
+using System.Text;
+using CHARK.GameManagement.Serialization;
namespace CHARK.GameManagement.Storage
{
@@ -7,22 +9,39 @@ namespace CHARK.GameManagement.Storage
///
internal sealed class EditorPrefsStorage : Storage
{
- public EditorPrefsStorage(ISerializer serializer, string keyPrefix = "") : base(serializer, keyPrefix)
+ public EditorPrefsStorage(ISerializer serializer, string pathPrefix = "") : base(serializer, pathPrefix)
{
}
- protected override string GetString(string path)
+ protected override Stream ReadStream(string path)
{
#if UNITY_EDITOR
- return UnityEditor.EditorPrefs.GetString(path);
+ var editorString = UnityEditor.EditorPrefs.GetString(path);
+ if (string.IsNullOrWhiteSpace(editorString))
+ {
+ return Stream.Null;
+ }
+
+ var bytes = Encoding.UTF8.GetBytes(editorString);
+ return new MemoryStream(bytes);
#else
- return default;
+ return Stream.Null;
+#endif
+ }
+
+ protected override void SaveString(string path, string value)
+ {
+#if UNITY_EDITOR
+ UnityEditor.EditorPrefs.SetString(path, value);
#endif
}
- protected override void SetString(string path, string value)
+ protected override void SaveStream(string path, Stream stream)
{
#if UNITY_EDITOR
+ using var streamReader = new StreamReader(stream);
+ var value = streamReader.ReadToEnd();
+
UnityEditor.EditorPrefs.SetString(path, value);
#endif
}
diff --git a/Packages/com.chark.game-management/Runtime/Storage/EditorPrefsStorage.cs.meta b/Packages/com.chark.game-management/Runtime/Storage/EditorPrefsStorage.cs.meta
index ee940dd..e09cda0 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/EditorPrefsStorage.cs.meta
+++ b/Packages/com.chark.game-management/Runtime/Storage/EditorPrefsStorage.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: 3795b11e96151c5438282149a64c9e21
+guid: 3df6529c17164ab68ceb29ac9dfb116a
timeCreated: 1669925611
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Runtime/Storage/FileStorage.cs b/Packages/com.chark.game-management/Runtime/Storage/FileStorage.cs
index 7474b3a..7b3df06 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/FileStorage.cs
+++ b/Packages/com.chark.game-management/Runtime/Storage/FileStorage.cs
@@ -1,34 +1,82 @@
using System.IO;
using CHARK.GameManagement.Serialization;
+using CHARK.GameManagement.Settings;
+using CHARK.GameManagement.Utilities;
namespace CHARK.GameManagement.Storage
{
internal sealed class FileStorage : Storage
{
private readonly string persistentDataPath;
+ private readonly IGameManagerSettingsProfile profile;
- public FileStorage(ISerializer serializer, string persistentDataPath, string pathPrefix = "") : base(serializer, pathPrefix)
+ public FileStorage(
+ ISerializer serializer,
+ IGameManagerSettingsProfile profile,
+ string persistentDataPath,
+ string pathPrefix = ""
+ ) : base(serializer, pathPrefix)
{
this.persistentDataPath = persistentDataPath;
+ this.profile = profile;
}
- protected override string GetString(string path)
+ protected override Stream ReadStream(string path)
{
var actualPath = GetFilePath(path);
if (File.Exists(actualPath) == false)
{
- return default;
+ if (profile.IsVerboseLogging)
+ {
+ Logging.LogWarning($"File does not exist: {actualPath}", GetType());
+ }
+
+ return Stream.Null;
}
- return File.ReadAllText(actualPath);
+ return File.OpenRead(actualPath);
}
- protected override void SetString(string path, string value)
+ protected override void SaveString(string path, string value)
{
var actualPath = GetFilePath(path);
+ var directory = Path.GetDirectoryName(actualPath);
+
+ if (string.IsNullOrWhiteSpace(directory) == false)
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ if (profile.IsVerboseLogging)
+ {
+ Logging.LogDebug($"Saving string to file: {actualPath}", GetType());
+ }
+
File.WriteAllText(actualPath, value);
}
+ protected override void SaveStream(string path, Stream stream)
+ {
+ var actualPath = GetFilePath(path);
+ var directory = Path.GetDirectoryName(actualPath);
+
+ if (string.IsNullOrWhiteSpace(directory) == false)
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ if (profile.IsVerboseLogging)
+ {
+ Logging.LogDebug(
+ $"Saving stream (length={stream.Length}, position={stream.Position}) to file: {actualPath}",
+ GetType()
+ );
+ }
+
+ using var fileStream = new FileStream(actualPath, FileMode.Create);
+ stream.CopyTo(fileStream);
+ }
+
protected override void Delete(string path)
{
var actualPath = GetFilePath(path);
@@ -37,15 +85,22 @@ protected override void Delete(string path)
return;
}
+ if (profile.IsVerboseLogging)
+ {
+ Logging.LogDebug($"Deleting file: {actualPath}", GetType());
+ }
+
File.Delete(actualPath);
}
private string GetFilePath(string path)
{
- var fileName = $"{path}.json";
- var actualPath = Path.Combine(persistentDataPath, fileName);
+ if (string.IsNullOrWhiteSpace(persistentDataPath))
+ {
+ return path;
+ }
- return actualPath;
+ return Path.Combine(persistentDataPath, path);
}
}
}
diff --git a/Packages/com.chark.game-management/Runtime/Storage/FileStorage.cs.meta b/Packages/com.chark.game-management/Runtime/Storage/FileStorage.cs.meta
index f2d41e0..59920a6 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/FileStorage.cs.meta
+++ b/Packages/com.chark.game-management/Runtime/Storage/FileStorage.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: 153e72cf9cea8144e8bffc11797d7e46
+guid: adee4469de0f4d7cb145558833da4d23
timeCreated: 1670271717
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Runtime/Storage/IStorage.cs b/Packages/com.chark.game-management/Runtime/Storage/IStorage.cs
index 06b9778..c5b38af 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/IStorage.cs
+++ b/Packages/com.chark.game-management/Runtime/Storage/IStorage.cs
@@ -1,4 +1,5 @@
-using System.Threading;
+using System.IO;
+using System.Threading;
using System.Threading.Tasks;
namespace CHARK.GameManagement.Storage
@@ -9,44 +10,81 @@ namespace CHARK.GameManagement.Storage
public interface IStorage
{
///
- /// Value from persistent game storage asynchronously or default if no value is
- /// could be retrieved.
+ /// true if persisted of type is
+ /// retrieved from given or false otherwise.
///
- public Task GetValueAsync(
+ public bool TryReadData(string path, out TData data);
+
+ ///
+ /// Persisted data retrieved from given
+ /// or an empty stream if no data is could be retrieved.
+ ///
+ public Stream ReadDataStream(string path);
+
+ ///
+ /// Persisted retrieved from given
+ /// asynchronously or default if no data is could be retrieved.
+ ///
+ ///
+ /// if could not be retrieved.
+ ///
+ public Task ReadDataAsync(
string path,
CancellationToken cancellationToken = default
);
- ///
- /// Get a value from persistent game storage.
- ///
- public bool TryGetValue(string path, out TValue value);
-
///
- /// Save a value to persistent game storage asynchronously.
+ /// Persisted data retrieved from given
+ /// asynchronously or an empty stream if no data is could be retrieved.
///
- public Task SetValueAsync(
+ public Task ReadDataStreamAsync(
string path,
- TValue value,
CancellationToken cancellationToken = default
);
///
- /// Save a value to persistent game storage.
+ /// Persist of type to to given
+ /// .
+ ///
+ public void SaveData(string path, TData data);
+
+ ///
+ /// Persist to .
+ /// asynchronously.
///
- public void SetValue(string path, TValue value);
+ public void SaveDataStream(string path, Stream stream);
///
- /// Delete value at given asynchronously.
+ /// Persist of type to .
+ /// asynchronously.
///
- public Task DeleteValueAsync(
+ public Task SaveDataAsync(
string path,
+ TData data,
CancellationToken cancellationToken = default
);
///
- /// Delete value at given .
+ /// Persist of data to given .
+ /// asynchronously.
///
- public void DeleteValue(string path);
+ public Task SaveDataStreamAsync(
+ string path,
+ Stream stream,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Delete persisted data from given .
+ ///
+ public void DeleteData(string path);
+
+ ///
+ /// Delete persisted data from given asynchronously.
+ ///
+ public Task DeleteDataAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ );
}
}
diff --git a/Packages/com.chark.game-management/Runtime/Storage/IStorage.cs.meta b/Packages/com.chark.game-management/Runtime/Storage/IStorage.cs.meta
index f197056..55c341e 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/IStorage.cs.meta
+++ b/Packages/com.chark.game-management/Runtime/Storage/IStorage.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: 63cf4c2156065ce498a83c99e0e73f55
+guid: 375fb5d7cc45456e81be7de326b35c08
timeCreated: 1669919582
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Runtime/Storage/NullStorage.cs b/Packages/com.chark.game-management/Runtime/Storage/NullStorage.cs
new file mode 100644
index 0000000..f90011b
--- /dev/null
+++ b/Packages/com.chark.game-management/Runtime/Storage/NullStorage.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using CHARK.GameManagement.Serialization;
+
+namespace CHARK.GameManagement.Storage
+{
+ ///
+ /// Storage which does nothing, used as a fall-back when regular storage cannot be created.
+ ///
+ internal sealed class NullStorage : Storage
+ {
+ public static NullStorage Instance { get; } = new();
+
+ private NullStorage() : base(DefaultSerializer.Instance, default)
+ {
+ }
+
+ public override bool TryReadData(string path, out TValue data)
+ {
+ data = default;
+ return false;
+ }
+
+ public override void SaveData(string path, TValue data)
+ {
+ }
+
+ protected override Stream ReadStream(string path)
+ {
+ return Stream.Null;
+ }
+
+ protected override void SaveString(string path, string value)
+ {
+ }
+
+ protected override void SaveStream(string path, Stream stream)
+ {
+ }
+
+ protected override void Delete(string path)
+ {
+ }
+ }
+}
diff --git a/Packages/com.chark.game-management/Runtime/Storage/DefaultStorage.cs.meta b/Packages/com.chark.game-management/Runtime/Storage/NullStorage.cs.meta
similarity index 54%
rename from Packages/com.chark.game-management/Runtime/Storage/DefaultStorage.cs.meta
rename to Packages/com.chark.game-management/Runtime/Storage/NullStorage.cs.meta
index 2e763e5..e11b18d 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/DefaultStorage.cs.meta
+++ b/Packages/com.chark.game-management/Runtime/Storage/NullStorage.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: ce724dba3e44b704380f4b972c5d202d
+guid: 31ce34677dfc433fa18370eef1d451c2
timeCreated: 1669928210
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Runtime/Storage/PlayerPrefsStorage.cs b/Packages/com.chark.game-management/Runtime/Storage/PlayerPrefsStorage.cs
index 376516b..9fefdaa 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/PlayerPrefsStorage.cs
+++ b/Packages/com.chark.game-management/Runtime/Storage/PlayerPrefsStorage.cs
@@ -1,4 +1,6 @@
-using CHARK.GameManagement.Serialization;
+using System.IO;
+using System.Text;
+using CHARK.GameManagement.Serialization;
using UnityEngine;
namespace CHARK.GameManagement.Storage
@@ -12,16 +14,32 @@ public PlayerPrefsStorage(ISerializer serializer, string pathPrefix = "") : base
{
}
- protected override string GetString(string path)
+ protected override Stream ReadStream(string path)
{
- return PlayerPrefs.GetString(path);
+ var prefsString = PlayerPrefs.GetString(path);
+ if (string.IsNullOrWhiteSpace(prefsString))
+ {
+ return Stream.Null;
+ }
+
+ var prefsBytes = Encoding.UTF8.GetBytes(prefsString);
+
+ return new MemoryStream(prefsBytes);
}
- protected override void SetString(string path, string value)
+ protected override void SaveString(string path, string value)
{
PlayerPrefs.SetString(path, value);
}
+ protected override void SaveStream(string path, Stream stream)
+ {
+ using var streamReader = new StreamReader(stream);
+ var value = streamReader.ReadToEnd();
+
+ PlayerPrefs.SetString(path, value);
+ }
+
protected override void Delete(string path)
{
PlayerPrefs.DeleteKey(path);
diff --git a/Packages/com.chark.game-management/Runtime/Storage/PlayerPrefsStorage.cs.meta b/Packages/com.chark.game-management/Runtime/Storage/PlayerPrefsStorage.cs.meta
index 18941be..709d90f 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/PlayerPrefsStorage.cs.meta
+++ b/Packages/com.chark.game-management/Runtime/Storage/PlayerPrefsStorage.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: ca7aedef44f0f4245aaa77fc97f64c43
+guid: 3bd652a013914f44959f0d3180367bd2
timeCreated: 1669925549
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Runtime/Storage/Storage.cs b/Packages/com.chark.game-management/Runtime/Storage/Storage.cs
index 4af8e1d..6162a36 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/Storage.cs
+++ b/Packages/com.chark.game-management/Runtime/Storage/Storage.cs
@@ -1,4 +1,6 @@
-using System.Threading;
+using System;
+using System.IO;
+using System.Threading;
using System.Threading.Tasks;
using CHARK.GameManagement.Serialization;
using Cysharp.Threading.Tasks;
@@ -16,16 +18,50 @@ protected Storage(ISerializer serializer, string pathPrefix)
this.pathPrefix = pathPrefix;
}
- public async Task GetValueAsync(
+ public virtual bool TryReadData(string path, out TValue data)
+ {
+ data = default;
+
+ if (TryGetFullPath(path, out var fullPath) == false)
+ {
+ return false;
+ }
+
+ using var stream = ReadStream(fullPath);
+ if (stream.Length == 0)
+ {
+ return false;
+ }
+
+ if (serializer.TryDeserializeStream(stream, out TValue deserializedValue) == false)
+ {
+ return false;
+ }
+
+ data = deserializedValue;
+ return true;
+ }
+
+ public Stream ReadDataStream(string path)
+ {
+ if (TryGetFullPath(path, out var fullPath) == false)
+ {
+ return Stream.Null;
+ }
+
+ return ReadStream(fullPath);
+ }
+
+ public async Task ReadDataAsync(
string path,
- CancellationToken cancellationToken
+ CancellationToken cancellationToken = default
)
{
await UniTask.SwitchToThreadPool();
try
{
- if (TryGetValue(path, out var value))
+ if (TryReadData(path, out var value))
{
return value;
}
@@ -35,44 +71,67 @@ CancellationToken cancellationToken
await UniTask.SwitchToMainThread(cancellationToken);
}
- return default;
+ throw new Exception($"Could not read data from path: {path}");
}
- public virtual bool TryGetValue(string path, out TValue value)
+ public async Task ReadDataStreamAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ )
{
- value = default;
+ if (TryGetFullPath(path, out var fullPath) == false)
+ {
+ return Stream.Null;
+ }
+
+ await UniTask.SwitchToThreadPool();
- if (TryGetFormattedPath(path, out var formattedPath) == false)
+ try
{
- return false;
+ return ReadStream(fullPath);
}
+ finally
+ {
+ await UniTask.SwitchToMainThread(cancellationToken);
+ }
+ }
- var stringValue = GetString(formattedPath);
- if (string.IsNullOrWhiteSpace(stringValue))
+ public virtual void SaveData(string path, TValue data)
+ {
+ if (TryGetFullPath(path, out var fullPath) == false)
{
- return false;
+ return;
}
- if (serializer.TryDeserializeValue(stringValue, out TValue deserializedValue) == false)
+ if (serializer.TrySerializeValue(data, out var serializedValue) == false)
{
- return false;
+ return;
}
- value = deserializedValue;
- return true;
+ SaveString(fullPath, serializedValue);
+ }
+
+ public void SaveDataStream(string path, Stream stream)
+ {
+ if (TryGetFullPath(path, out var fullPath) == false)
+ {
+ return;
+ }
+
+ SaveStream(fullPath, stream);
}
- public async Task SetValueAsync(
+ public async Task SaveDataAsync(
string path,
- TValue value,
- CancellationToken cancellationToken
+ TValue data,
+ CancellationToken cancellationToken = default
)
{
await UniTask.SwitchToThreadPool();
try
{
- SetValue(path, value);
+ SaveData(path, data);
}
finally
{
@@ -80,22 +139,41 @@ CancellationToken cancellationToken
}
}
- public virtual void SetValue(string path, TValue value)
+ public async Task SaveDataStreamAsync(
+ string path,
+ Stream stream,
+ CancellationToken cancellationToken = default
+ )
{
- if (TryGetFormattedPath(path, out var formattedPath) == false)
+ if (TryGetFullPath(path, out var fullPath) == false)
{
return;
}
- if (serializer.TrySerializeValue(value, out var serializedValue) == false)
+ await UniTask.SwitchToThreadPool();
+
+ try
{
- return;
+ SaveStream(fullPath, stream);
}
+ finally
+ {
+ await UniTask.SwitchToMainThread(cancellationToken);
+ }
+ }
- SetString(formattedPath, serializedValue);
+ public void DeleteData(string path)
+ {
+ if (TryGetFullPath(path, out var fullPath))
+ {
+ Delete(fullPath);
+ }
}
- public async Task DeleteValueAsync(string path, CancellationToken cancellationToken)
+ public async Task DeleteDataAsync(
+ string path,
+ CancellationToken cancellationToken = default
+ )
{
await UniTask.SwitchToThreadPool();
@@ -109,39 +187,36 @@ public async Task DeleteValueAsync(string path, CancellationToken cancellationTo
}
}
- public void DeleteValue(string path)
- {
- if (TryGetFormattedPath(path, out var formattedPath))
- {
- Delete(formattedPath);
- }
- }
-
///
- /// String retrieved using given .
+ /// Stream retrieved using given .
///
- protected abstract string GetString(string path);
+ protected abstract Stream ReadStream(string path);
+
+ ///
+ /// Store a to given .
+ ///
+ protected abstract void SaveString(string path, string value);
///
- /// Store a at given .
+ /// Store a to given .
///
- protected abstract void SetString(string path, string value);
+ protected abstract void SaveStream(string path, Stream stream);
///
/// Delete value at given .
///
protected abstract void Delete(string path);
- private bool TryGetFormattedPath(string path, out string formattedPath)
+ private bool TryGetFullPath(string path, out string fullPath)
{
- formattedPath = default;
+ fullPath = default;
if (string.IsNullOrWhiteSpace(path))
{
return false;
}
- formattedPath = pathPrefix + path;
+ fullPath = pathPrefix + path;
return true;
}
}
diff --git a/Packages/com.chark.game-management/Runtime/Storage/Storage.cs.meta b/Packages/com.chark.game-management/Runtime/Storage/Storage.cs.meta
index d4cbf81..710aaf9 100644
--- a/Packages/com.chark.game-management/Runtime/Storage/Storage.cs.meta
+++ b/Packages/com.chark.game-management/Runtime/Storage/Storage.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: 2bff78b09def57040bc3ed7af6a9ae3c
+guid: ea91cadd8a0c4a14b19e67360efea6bf
timeCreated: 1669919761
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Runtime/Utilities/Logging.cs b/Packages/com.chark.game-management/Runtime/Utilities/Logging.cs
index 60987af..47fc2c0 100644
--- a/Packages/com.chark.game-management/Runtime/Utilities/Logging.cs
+++ b/Packages/com.chark.game-management/Runtime/Utilities/Logging.cs
@@ -74,5 +74,15 @@ internal static void LogError(string message, Object owner)
Debug.LogError($"[{name}]: {message}", owner);
#endif
}
+
+ internal static void LogException(Exception exception, Type owner)
+ {
+ Debug.LogException(exception);
+ }
+
+ internal static void LogException(Exception exception, Object owner)
+ {
+ Debug.LogException(exception, owner);
+ }
}
}
diff --git a/Packages/com.chark.game-management/Samples/Counters/Scripts/StorageSystem.cs b/Packages/com.chark.game-management/Samples/Counters/Scripts/StorageSystem.cs
index f3e87e1..1e77a35 100644
--- a/Packages/com.chark.game-management/Samples/Counters/Scripts/StorageSystem.cs
+++ b/Packages/com.chark.game-management/Samples/Counters/Scripts/StorageSystem.cs
@@ -6,13 +6,13 @@ internal sealed class StorageSystem : SimpleSystem, IStorageSystem
{
public long LoadFixedUpdateCount()
{
- var key = GetStorageKey(nameof(CounterSystem.FixedUpdateCount));
+ var path = GetPath(nameof(CounterSystem.FixedUpdateCount));
// Game Manager provides an abstraction for retrieving values. It also supports json
// serialization by default.
- if (GameManager.TryGetRuntimeValue(key, out var value))
+ if (GameManager.TryReadData(path, out var data))
{
- return value;
+ return data;
}
return 0;
@@ -20,10 +20,10 @@ public long LoadFixedUpdateCount()
public long LoadUpdateCount()
{
- var key = GetStorageKey(nameof(CounterSystem.UpdateCount));
- if (GameManager.TryGetRuntimeValue(key, out var value))
+ var path = GetPath(nameof(CounterSystem.UpdateCount));
+ if (GameManager.TryReadData(path, out var data))
{
- return value;
+ return data;
}
return 0;
@@ -31,17 +31,17 @@ public long LoadUpdateCount()
public void SaveFixedUpdateCount(long count)
{
- var key = GetStorageKey(nameof(CounterSystem.FixedUpdateCount));
- GameManager.SetRuntimeValue(key, count);
+ var path = GetPath(nameof(CounterSystem.FixedUpdateCount));
+ GameManager.SaveData(path, count);
}
public void SaveUpdateCount(long count)
{
- var key = GetStorageKey(nameof(CounterSystem.UpdateCount));
- GameManager.SetRuntimeValue(key, count);
+ var path = GetPath(nameof(CounterSystem.UpdateCount));
+ GameManager.SaveData(path, count);
}
- private static string GetStorageKey(string suffix)
+ private static string GetPath(string suffix)
{
return $"{nameof(StorageSystem)}_${suffix}";
}
diff --git a/Packages/com.chark.game-management/Tests/Editor/EditorPrefsStorageTest.cs b/Packages/com.chark.game-management/Tests/Editor/EditorPrefsStorageTest.cs
index 83cbbfc..4b4c154 100644
--- a/Packages/com.chark.game-management/Tests/Editor/EditorPrefsStorageTest.cs
+++ b/Packages/com.chark.game-management/Tests/Editor/EditorPrefsStorageTest.cs
@@ -4,6 +4,7 @@
namespace CHARK.GameManagement.Tests.Editor
{
+ // ReSharper disable once UnusedType.Global
internal sealed class EditorPrefsStorageTest : StorageTest
{
protected override IStorage CreateStorage()
@@ -11,19 +12,19 @@ protected override IStorage CreateStorage()
return new EditorPrefsStorage(DefaultSerializer.Instance);
}
- protected override string GetString(string key)
+ protected override string ReadString(string path)
{
- return EditorPrefs.GetString(key);
+ return EditorPrefs.GetString(path);
}
- protected override void SetString(string key, string value)
+ protected override void SaveString(string path, string data)
{
- EditorPrefs.SetString(key, value);
+ EditorPrefs.SetString(path, data);
}
- protected override void DeleteString(string key)
+ protected override void DeleteString(string path)
{
- EditorPrefs.DeleteKey(key);
+ EditorPrefs.DeleteKey(path);
}
}
}
diff --git a/Packages/com.chark.game-management/Tests/Editor/EditorPrefsStorageTest.cs.meta b/Packages/com.chark.game-management/Tests/Editor/EditorPrefsStorageTest.cs.meta
index 733cb6a..2bd5415 100644
--- a/Packages/com.chark.game-management/Tests/Editor/EditorPrefsStorageTest.cs.meta
+++ b/Packages/com.chark.game-management/Tests/Editor/EditorPrefsStorageTest.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: 2368299ec355e784089fd85a2f2624a5
+guid: 43a0363377e54882ad5aa7514c99a3e9
timeCreated: 1669924783
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Tests/Editor/FileStorageTest.cs b/Packages/com.chark.game-management/Tests/Editor/FileStorageTest.cs
new file mode 100644
index 0000000..9d1d1fd
--- /dev/null
+++ b/Packages/com.chark.game-management/Tests/Editor/FileStorageTest.cs
@@ -0,0 +1,51 @@
+using System.IO;
+using CHARK.GameManagement.Serialization;
+using CHARK.GameManagement.Storage;
+using UnityEngine;
+
+namespace CHARK.GameManagement.Tests.Editor
+{
+ // ReSharper disable once UnusedType.Global
+ internal sealed class FileStorageTest : StorageTest
+ {
+ protected override IStorage CreateStorage()
+ {
+ return new FileStorage(
+ serializer: DefaultSerializer.Instance,
+ profile: GameManagerTestProfile.Instance,
+ persistentDataPath: Application.persistentDataPath,
+ pathPrefix: "Tests/"
+ );
+ }
+
+ protected override string ReadString(string path)
+ {
+ var actualPath = GetTestPath(path);
+ return File.ReadAllText(actualPath);
+ }
+
+ protected override void SaveString(string path, string data)
+ {
+ var actualPath = GetTestPath(path);
+ var directory = Path.GetDirectoryName(actualPath);
+
+ if (string.IsNullOrWhiteSpace(directory) == false)
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ File.WriteAllText(actualPath, data);
+ }
+
+ protected override void DeleteString(string path)
+ {
+ var actualPath = GetTestPath(path);
+ File.Delete(actualPath);
+ }
+
+ private static string GetTestPath(string path)
+ {
+ return Path.Combine(Application.persistentDataPath, "Tests", path);
+ }
+ }
+}
diff --git a/Packages/com.chark.game-management/Tests/Editor/FileStorageTest.cs.meta b/Packages/com.chark.game-management/Tests/Editor/FileStorageTest.cs.meta
new file mode 100644
index 0000000..cfdfc20
--- /dev/null
+++ b/Packages/com.chark.game-management/Tests/Editor/FileStorageTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5634da6184fd4af3aa9bd12873bc4b66
+timeCreated: 1697718487
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Tests/Editor/GameManagerTestProfile.cs b/Packages/com.chark.game-management/Tests/Editor/GameManagerTestProfile.cs
new file mode 100644
index 0000000..76980ce
--- /dev/null
+++ b/Packages/com.chark.game-management/Tests/Editor/GameManagerTestProfile.cs
@@ -0,0 +1,29 @@
+using CHARK.GameManagement.Settings;
+
+namespace CHARK.GameManagement.Tests.Editor
+{
+ internal sealed class GameManagerTestProfile : IGameManagerSettingsProfile
+ {
+ internal static GameManagerTestProfile Instance { get; } = new();
+
+ private GameManagerTestProfile()
+ {
+ }
+
+ public string Name => "Test Profile";
+
+ public bool IsInstantiateAutomatically => false;
+
+ public InstantiationMode InstantiationMode => InstantiationMode.BeforeSceneLoad;
+
+ public bool IsDontDestroyOnLoad => false;
+
+ public bool IsVerboseLogging => true;
+
+ public bool TryGetGameManagerPrefab(out GameManager prefab)
+ {
+ prefab = default;
+ return false;
+ }
+ }
+}
diff --git a/Packages/com.chark.game-management/Tests/Editor/GameManagerTestProfile.cs.meta b/Packages/com.chark.game-management/Tests/Editor/GameManagerTestProfile.cs.meta
new file mode 100644
index 0000000..514511b
--- /dev/null
+++ b/Packages/com.chark.game-management/Tests/Editor/GameManagerTestProfile.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 79d514ff5ec84dff97c18442e169a573
+timeCreated: 1697718546
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Tests/Editor/PlayerPrefsStorageTest.cs b/Packages/com.chark.game-management/Tests/Editor/PlayerPrefsStorageTest.cs
index 977ac6b..8856024 100644
--- a/Packages/com.chark.game-management/Tests/Editor/PlayerPrefsStorageTest.cs
+++ b/Packages/com.chark.game-management/Tests/Editor/PlayerPrefsStorageTest.cs
@@ -4,6 +4,7 @@
namespace CHARK.GameManagement.Tests.Editor
{
+ // ReSharper disable once UnusedType.Global
internal sealed class PlayerPrefsStorageTest : StorageTest
{
protected override IStorage CreateStorage()
@@ -11,19 +12,19 @@ protected override IStorage CreateStorage()
return new PlayerPrefsStorage(DefaultSerializer.Instance);
}
- protected override string GetString(string key)
+ protected override string ReadString(string path)
{
- return PlayerPrefs.GetString(key);
+ return PlayerPrefs.GetString(path);
}
- protected override void SetString(string key, string value)
+ protected override void SaveString(string path, string data)
{
- PlayerPrefs.SetString(key, value);
+ PlayerPrefs.SetString(path, data);
}
- protected override void DeleteString(string key)
+ protected override void DeleteString(string path)
{
- PlayerPrefs.DeleteKey(key);
+ PlayerPrefs.DeleteKey(path);
}
}
}
diff --git a/Packages/com.chark.game-management/Tests/Editor/PlayerPrefsStorageTest.cs.meta b/Packages/com.chark.game-management/Tests/Editor/PlayerPrefsStorageTest.cs.meta
index 50e9f62..32ed0ae 100644
--- a/Packages/com.chark.game-management/Tests/Editor/PlayerPrefsStorageTest.cs.meta
+++ b/Packages/com.chark.game-management/Tests/Editor/PlayerPrefsStorageTest.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: dae3f11f888aa314394e2513084cbbd5
+guid: 79ddf9e1bf7c4a8dab7a9a635b247d1c
timeCreated: 1669922570
\ No newline at end of file
diff --git a/Packages/com.chark.game-management/Tests/Editor/StorageTest.cs b/Packages/com.chark.game-management/Tests/Editor/StorageTest.cs
index a41aa7c..ca91af1 100644
--- a/Packages/com.chark.game-management/Tests/Editor/StorageTest.cs
+++ b/Packages/com.chark.game-management/Tests/Editor/StorageTest.cs
@@ -1,4 +1,6 @@
using System.Globalization;
+using System.IO;
+using System.Text;
using CHARK.GameManagement.Storage;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
@@ -13,7 +15,7 @@ internal abstract class StorageTest
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
- private string Key => GetType().Name;
+ private string Path => GetType().Name;
private IStorage storage;
@@ -26,122 +28,201 @@ public void SetUp()
[TearDown]
public void TearDown()
{
- DeleteString(Key);
+ DeleteString(Path);
}
[Test]
- public void ShouldGetPrimitiveValue()
+ public void ShouldReadPrimitiveData()
{
// Given:
- // - Persisted primitive float value of 1.
- const float expectedValue = 1f;
- SetString(Key, expectedValue.ToString(CultureInfo.InvariantCulture));
+ // - Persisted primitive float data of 1.
+ const float expectedData = 1f;
+ SaveString(Path, expectedData.ToString(CultureInfo.InvariantCulture));
// When:
- // - Retrieving value from storage.
- if (storage.TryGetValue(Key, out float actualValue) == false)
+ // - Retrieving data from storage.
+ if (storage.TryReadData(Path, out float actualData) == false)
{
- Assert.Fail("Could not get Runtime primitive value");
+ Assert.Fail($"Could not {nameof(storage.TryReadData)} from primitive");
}
// Then:
- // - Value should be retrieved properly.
- Assert.AreEqual(expectedValue, actualValue);
+ // - Data should be retrieved properly.
+ Assert.AreEqual(expectedData, actualData);
}
[Test]
- public void ShouldGetStructValue()
+ public void ShouldReadStructData()
{
// Given:
- // - Persisted struct value.
- var expectedValue = new TestStructData("hello");
- SetString(Key, SerializeObject(expectedValue));
+ // - Persisted struct data.
+ var expectedData = new TestStructData("hello");
+ SaveString(Path, SerializeObject(expectedData));
// When:
- // - Retrieving value from storage.
- if (storage.TryGetValue(Key, out TestStructData actualValue) == false)
+ // - Retrieving data from storage.
+ if (storage.TryReadData(Path, out TestStructData actualData) == false)
{
- Assert.Fail("Could not get Runtime struct value");
+ Assert.Fail($"Could not {nameof(storage.TryReadData)} from struct");
}
// Then:
- // - Value (contents) should be retrieved properly.
- Assert.AreEqual(expectedValue.Text, actualValue.Text);
+ // - Data (contents) should be retrieved properly.
+ Assert.AreEqual(expectedData.Text, actualData.Text);
}
[Test]
- public void ShouldGetObjectValue()
+ public void ShouldReadObjectData()
{
// Given:
- // - Persisted object value.
- var expectedValue = new TestObjectData("hello");
- SetString(Key, SerializeObject(expectedValue));
+ // - Persisted object data.
+ var expectedData = new TestObjectData("hello");
+ SaveString(Path, SerializeObject(expectedData));
// When:
- // - Retrieving value from storage.
- if (storage.TryGetValue(Key, out TestObjectData actualValue) == false)
+ // - Retrieving data from storage.
+ if (storage.TryReadData(Path, out TestObjectData actualData) == false)
{
- Assert.Fail("Could not get Runtime object value");
+ Assert.Fail($"Could not {nameof(storage.TryReadData)} from object");
}
// Then:
- // - Value (contents) should be retrieved properly.
- Assert.AreEqual(expectedValue.Text, actualValue.Text);
+ // - Data (contents) should be retrieved properly.
+ Assert.AreEqual(expectedData.Text, actualData.Text);
}
[Test]
- public void ShouldSetPrimitiveValue()
+ public void ShouldReadNoDataOnMissingData()
{
// Given:
- // - Primitive float value of 1.
- const float expectedValue = 1f;
+ // - No persisted data.
+ DeleteString(Path);
// When:
- // - Persisting value.
- storage.SetValue(Key, expectedValue);
+ // - Retrieving data from storage.
+ var isRead = storage.TryReadData(Path, out var data);
// Then:
- // - Value should be persisted properly.
- var actualValue = float.Parse(GetString(Key));
- Assert.AreEqual(expectedValue, actualValue);
+ // - Data should not be retrieved.
+ Assert.IsFalse(isRead);
+ Assert.IsNull(data);
}
[Test]
- public void ShouldSetStructValue()
+ public void ShouldReadStreamObjectData()
{
// Given:
- // - Struct value.
- var value = new TestStructData("hello there");
+ // - Persisted object data.
+ var expectedData = new TestObjectData("hello");
+ SaveString(Path, SerializeObject(expectedData));
// When:
- // - Persisting value.
- storage.SetValue(Key, value);
+ // - Retrieving data from storage.
+ var actualStream = storage.ReadDataStream(Path);
+ if (actualStream.Length == 0)
+ {
+ Assert.Fail($"Could not {nameof(storage.ReadDataStream)} from object");
+ }
+
+ // Then:
+ // - Data (contents) should be retrieved properly.
+ var actualData = DeserializeObject(actualStream);
+
+ Assert.AreEqual(expectedData.Text, actualData.Text);
+ }
+
+ [Test]
+ public void ShouldReadEmptyStreamOnMissingData()
+ {
+ // Given:
+ // - No persisted data.
+ DeleteString(Path);
+
+ // When:
+ // - Retrieving data from storage.
+ var stream = storage.ReadDataStream(Path);
// Then:
- // - Value should be persisted properly.
- var expectedValue = SerializeObject(value);
- var actualValue = GetString(Key);
+ // - Data (contents) should be retrieved properly.
+ Assert.AreEqual(0, stream.Length);
+ }
+
+ [Test]
+ public void ShouldSavePrimitiveData()
+ {
+ // Given:
+ // - Primitive float data of 1.
+ const float expectedData = 1f;
+
+ // When:
+ // - Persisting data.
+ storage.SaveData(Path, expectedData);
- Assert.AreEqual(expectedValue, actualValue);
+ // Then:
+ // - Data should be persisted properly.
+ var actualData = float.Parse(ReadString(Path));
+ Assert.AreEqual(expectedData, actualData);
}
[Test]
- public void ShouldSetObjectValue()
+ public void ShouldSaveStructData()
{
// Given:
- // - Object value.
- var value = new TestObjectData("hello there");
+ // - Struct data.
+ var data = new TestStructData("hello there");
// When:
- // - Persisting value.
- storage.SetValue(Key, value);
+ // - Persisting data.
+ storage.SaveData(Path, data);
// Then:
- // - Value should be persisted properly.
- var expectedValue = SerializeObject(value);
- var actualValue = GetString(Key);
+ // - Data should be persisted properly.
+ var expectedData = SerializeObject(data);
+ var actualData = ReadString(Path);
- Assert.AreEqual(expectedValue, actualValue);
+ Assert.AreEqual(expectedData, actualData);
+ }
+
+ [Test]
+ public void ShouldSaveObjectData()
+ {
+ // Given:
+ // - Data object.
+ var data = new TestObjectData("hello there");
+
+ // When:
+ // - Persisting data.
+ storage.SaveData(Path, data);
+
+ // Then:
+ // - Data should be persisted properly.
+ var expectedData = SerializeObject(data);
+ var actualData = ReadString(Path);
+
+ Assert.AreEqual(expectedData, actualData);
+ }
+
+ [Test]
+ public void ShouldSaveStreamObjectData()
+ {
+ // Given:
+ // - Object data.
+ var data = new TestObjectData("hello there");
+ var expectedData = SerializeObject(data);
+
+ // When:
+ // - Persisting data.
+ using var stream = new MemoryStream(
+ Encoding.UTF8.GetBytes(expectedData)
+ );
+
+ storage.SaveDataStream(Path, stream);
+
+ // Then:
+ // - Data should be persisted properly.
+ var actualData = ReadString(Path);
+
+ Assert.AreEqual(expectedData, actualData);
}
///
@@ -150,26 +231,31 @@ public void ShouldSetObjectValue()
protected abstract IStorage CreateStorage();
///
- /// Raw json value retrieved by given .
+ /// Raw json data retrieved by given .
///
- protected abstract string GetString(string key);
+ protected abstract string ReadString(string path);
///
- /// Set raw json value retrieved at given .
+ /// Set raw json data retrieved at given .
///
- protected abstract void SetString(string key, string value);
+ protected abstract void SaveString(string path, string data);
///
- /// Delete raw json stored at given .
+ /// Delete raw json stored at given .
///
- protected abstract void DeleteString(string key);
+ protected abstract void DeleteString(string path);
- ///
- /// json
string created from given
- ///
- protected string SerializeObject(T obj)
+ private static TObject DeserializeObject(Stream stream)
+ {
+ using var reader = new StreamReader(stream, Encoding.UTF8);
+ var @object = reader.ReadToEnd();
+
+ return JsonConvert.DeserializeObject(@object, SerializerSettings);
+ }
+
+ private static string SerializeObject(TObject @object)
{
- return JsonConvert.SerializeObject(obj, SerializerSettings);
+ return JsonConvert.SerializeObject(@object, SerializerSettings);
}
private class TestObjectData
diff --git a/Packages/com.chark.game-management/Tests/Editor/StorageTest.cs.meta b/Packages/com.chark.game-management/Tests/Editor/StorageTest.cs.meta
index a4966bc..e909ecd 100644
--- a/Packages/com.chark.game-management/Tests/Editor/StorageTest.cs.meta
+++ b/Packages/com.chark.game-management/Tests/Editor/StorageTest.cs.meta
@@ -1,3 +1,3 @@
fileFormatVersion: 2
-guid: 84adec182f69dc3449510989d0ddb724
+guid: 5e33c627119c4b06a548b9e3ef9242b6
timeCreated: 1670270154
\ No newline at end of file
diff --git a/Packages/manifest.json b/Packages/manifest.json
index 87310e9..b66c295 100644
--- a/Packages/manifest.json
+++ b/Packages/manifest.json
@@ -2,6 +2,7 @@
"dependencies": {
"com.unity.ide.rider": "3.0.25",
"com.unity.test-framework": "1.1.33",
- "com.unity.textmeshpro": "3.0.6"
+ "com.unity.textmeshpro": "3.0.6",
+ "com.unity.modules.unitywebrequest": "1.0.0"
}
}
diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json
index 12c6b3f..b612f30 100644
--- a/Packages/packages-lock.json
+++ b/Packages/packages-lock.json
@@ -77,6 +77,12 @@
"depth": 2,
"source": "builtin",
"dependencies": {}
+ },
+ "com.unity.modules.unitywebrequest": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
}
}
}