Resolve WES-117 "Persistent data handling"

This commit is contained in:
Dries Van Schuylenbergh
2023-04-04 17:00:47 +00:00
parent 3499e61bb0
commit 5f4408063f
82 changed files with 1963 additions and 1190 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 17374f544297b194aa5517c196ca2f07
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
/// <summary>
/// Enum for easy indexing and checking if a course is of a certain kind
/// </summary>
public enum CourseIndex
{
FINGERSPELLING,
CLOTHING,
ANIMALS,
FOOD,
HOBBIES,
HOUSE,
FAMILY
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5bf21dee022ed0489face1c734359de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
/// <summary>
/// Enum for easy indexing and checking if a minigame is of a certain kind
/// </summary>
public enum MinigameIndex
{
SPELLING_BEE,
HANGMAN,
JUST_SIGN
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97282ff3b465e3c4682d218b3819b2e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,427 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
/// <summary>
/// PersistentDataController singleton
/// </summary>
public class PersistentDataController
{
/// <summary>
/// The instance controlling the singleton
/// </summary>
private static PersistentDataController instance = null;
/// <summary>
/// Current implementation version of the PersistentDataController
/// </summary>
/// <remarks>MSB represent sprint version, LSB represent subversion</remarks>
public static readonly int VERSION = 0x04_01;
/// <summary>
/// Path of the <c>.json</c>-file to store all serialized data
/// </summary>
public static string PATH = null;
/// <summary>
/// Class to hold a list of data records
/// </summary>
[Serializable]
public class PersistentDataContainer
{
/// <summary>
/// A helper class for handling the stored progress
/// </summary>
[Serializable]
public class PersistentDataEntry
{
/// <summary>
/// The key, used to reference the data object
/// </summary>
public string key;
/// <summary>
/// The object, representated as a list of byte (which can be serialized)
/// </summary>
public List<byte> data = new List<byte>();
public PersistentDataEntry(string key, byte[] data) : this(key, data.ToList())
{ }
public PersistentDataEntry(string key, List<byte> data)
{
this.key = key;
this.data = data;
}
}
/// <summary>
/// List of data records
/// </summary>
public List<PersistentDataEntry> entries = new List<PersistentDataEntry>();
/// <summary>
/// Update the value of a certain key,
/// or add a new value if the key was not present.
/// </summary>
/// <typeparam name="T">The type of the data to be added/updated</typeparam>
/// <param name="key">The key, used for referencing the data</param>
/// <param name="data">The object of type <typeparamref name="T"/></param>
/// <returns><c>true</c> if successful, <c>false</c> otherwise</returns>
public bool Set<T>(string key, T data)
{
if (data == null)
return false;
PersistentDataEntry entry = entries.Find(x => x.key == key);
// Hacky serialization stuff
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, data);
if (entry != null)
{
entry.data.Clear();
entry.data.AddRange(ms.ToArray());
}
else
{
entries.Add(new PersistentDataEntry(key, ms.ToArray()));
}
return true;
}
}
/// <summary>
/// Get the data object of a certain key
/// </summary>
/// <typeparam name="T">The type of the data object</typeparam>
/// <param name="key">The key referencing the data object</param>
/// <returns>The data, cast to a type <typeparamref name="T"/></returns>
/// <exception cref="KeyNotFoundException"></exception>
/// <exception cref="InvalidCastException"></exception>
public T Get<T>(string key)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
// Find the correct key
foreach (PersistentDataEntry entry in entries)
{
if (entry.key == key)
{
// Hacky serialization stuff
byte[] data = entry.data.ToArray();
ms.Write(data, 0, data.Length);
ms.Seek(0, SeekOrigin.Begin);
return (T)bf.Deserialize(ms);
}
}
}
// Raise an exception when key is not found
throw new KeyNotFoundException();
}
/// <summary>
/// Remove a key-value from the data.
/// </summary>
/// <param name="key">The key referencing the data object</param>
/// <exception cref="KeyNotFoundException"></exception>
public void Remove(string key)
{
if (!Has(key))
throw new KeyNotFoundException();
entries.Remove(entries.Find(x => x.key == key));
}
/// <summary>
/// Remove and return value from the data.
/// </summary>
/// <typeparam name="T">The type of the data object</typeparam>
/// <param name="key">The key referencing the data object</param>
/// <param name="save">Whether the removal of the data should also be saved to disk</param>
/// <returns></returns>
public T Pop<T>(string key)
{
T data = Get<T>(key);
Remove(key);
return data;
}
/// <summary>
/// Check whether a key is present
/// </summary>
/// <param name="key">The key to check</param>
/// <returns>true if a item can be found with the specified key</returns>
public bool Has(string key)
{
return entries.Find(x => x.key == key) != null;
}
}
/// <summary>
/// Stored user data record
/// </summary>
[Serializable]
public class SavedUserData : PersistentDataContainer
{
public string username = null;
public int avatarIndex = -1;
public double playtime = 0.0;
public List<SavedMinigameProgress> minigames = new List<SavedMinigameProgress>();
public List<SavedCourseProgress> courses = new List<SavedCourseProgress>();
}
/// <summary>
/// Stored course progress data record
/// </summary>
[Serializable]
public class SavedCourseProgress : PersistentDataContainer
{
public CourseIndex courseIndex;
public float progress = -1.0f;
}
/// <summary>
/// Stored minigame progress data record
/// </summary>
[Serializable]
public class SavedMinigameProgress : PersistentDataContainer
{
public MinigameIndex minigameIndex;
public List<Score> latestScores = new List<Score>();
public List<Score> highestScores = new List<Score>();
}
/// <summary>
/// Stored WeSign data record
/// </summary>
[Serializable]
private class SavedDataStructure
{
public int version = VERSION;
public List<SavedUserData> users = new List<SavedUserData>();
public int currentUser = -1;
public MinigameIndex currentMinigame;
public CourseIndex currentCourse;
public ThemeIndex currentTheme;
}
/// <summary>
/// The object holding the data references
/// </summary>
private SavedDataStructure json = new SavedDataStructure();
/// <summary>
/// Get the instance loaded by the singleton
/// </summary>
/// <returns><c>PersistentDataController</c> instance</returns>
public static PersistentDataController GetInstance()
{
// Create a new instance if non exists
if (instance == null)
{
if (PATH == null)
PersistentDataController.PATH = $"{Application.persistentDataPath}/wesign_saved_data.json";
instance = new PersistentDataController();
}
return instance;
}
/// <summary>
/// PersistentDataController contructor
/// </summary>
private PersistentDataController()
{
Load();
}
/// <summary>
/// Clear everything stored in the PersistentDataController, won't save to disk
/// </summary>
public void Clear()
{
json.users.Clear();
json.currentUser = -1;
}
/// <summary>
/// Save all data to disk
/// </summary>
public void Save()
{
string text = JsonUtility.ToJson(json);
File.CreateText(PATH).Close();
File.WriteAllText(PATH, text);
}
/// <summary>
/// Override current data with the data from disk, will just clear if no data was found.
/// </summary>
/// <param name="overrideOnFail"><c>true</c> if you want to override the existing file if it exists and the loading failed.</param>
/// <remarks>If the data on disk is outdated (version number is lower than the current version), the loading will also fail</remarks>
/// <returns><c>true</c> if successful, <c>false</c> otherwise</returns>
public bool Load(bool overrideOnFail = true)
{
Clear();
if (!File.Exists(PATH))
goto failed;
try
{
string text = File.ReadAllText(PATH);
SavedDataStructure newJson = JsonUtility.FromJson<SavedDataStructure>(text);
if (newJson == null || newJson.version < VERSION)
goto failed;
json = newJson;
return true;
}
catch (Exception) { goto failed; }
failed:
if (overrideOnFail)
Save();
return false;
}
/// <summary>
/// Add a user to the WeSign data record
/// </summary>
/// <param name="user">User data record</param>
/// <param name="save">Whether to save the addition immediately to disk</param>
public void AddUser(SavedUserData user, bool save = true)
{
if (json.users.Count == 0)
json.currentUser = 0;
json.users.Add(user);
if (save)
Save();
}
/// <summary>
/// Get a list of all user data records
/// </summary>
/// <returns></returns>
public List<SavedUserData> GetUsers()
{
return json.users;
}
/// <summary>
/// Get the index of the current user
/// </summary>
/// <returns></returns>
public int GetCurrentUser()
{
return json.currentUser;
}
/// <summary>
/// Set the index of the current record
/// </summary>
/// <param name="index">New index</param>
/// <param name="save">Whether to save the change immediately to disk</param>
/// <exception cref="IndexOutOfRangeException"></exception>
public void SetCurrentUser(int index, bool save = true)
{
if (index < 0 || json.users.Count <= index)
throw new IndexOutOfRangeException();
json.currentUser = index;
if (save)
Save();
}
/// <summary>
/// Remove a user data record
/// </summary>
/// <param name="index">Index of the user</param>
/// <param name="save">Whether to save the deletion immediately to disk</param>
/// <exception cref="IndexOutOfRangeException"></exception>
public void DeleteUser(int index, bool save = true)
{
if (index < 0 || json.users.Count <= index)
throw new IndexOutOfRangeException();
if (0 < json.currentUser && index <= json.currentUser)
json.currentUser--;
json.users.RemoveAt(index);
if (save)
Save();
}
/// <summary>
/// Get the current course
/// </summary>
/// <returns></returns>
public CourseIndex GetCurrentCourse()
{
return json.currentCourse;
}
/// <summary>
/// Set the current course
/// </summary>
/// <param name="course">New course index</param>
/// <param name="save">Whether to save the change immediately to disk</param>
public void SetCurrentCourse(CourseIndex course, bool save = true)
{
json.currentCourse = course;
if (save)
Save();
}
/// <summary>
/// Get the current minigame
/// </summary>
/// <returns></returns>
public MinigameIndex GetCurrentMinigame()
{
return json.currentMinigame;
}
/// <summary>
/// Set the current minigame
/// </summary>
/// <param name="minigame">New minigame index</param>
/// <param name="save">Whether to save the change immediately to disk</param>
public void SetCurrentMinigame(MinigameIndex minigame, bool save = true)
{
json.currentMinigame = minigame;
if (save)
Save();
}
/// <summary>
/// Get the current theme
/// </summary>
/// <returns></returns>
public ThemeIndex GetCurrentTheme()
{
return json.currentTheme;
}
/// <summary>
/// Set the current theme
/// </summary>
/// <param name="theme">New theme index</param>
/// <param name="save">Whether to save the change immediately to disk</param>
public void SetCurrentTheme(ThemeIndex theme, bool save = true)
{
json.currentTheme = theme;
if (save)
Save();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 164b1accbeff688429a4311f1e40bd59
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using System;
/// <summary>
/// Score class
/// </summary>
[Serializable]
public class Score
{
public int scoreValue;
public string time;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16056ca3e1523f78cbd727cea2bfe047
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
{
"name": "SystemArchitecture",
"rootNamespace": "",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e83ddf9a537a96b4a804a16bb7872ec1
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,104 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
/// <summary>
/// SystemController singleton
/// </summary>
public class SystemController
{
/// <summary>
/// The instance controlling the singleton
/// </summary>
private static SystemController instance = null;
/// <summary>
/// Stack of the loaded scenes, used to easily go back to previous scenes
/// </summary>
private Stack<int> sceneStack = new Stack<int>();
/// <summary>
/// Get the instance loaded by the singleton
/// </summary>
/// <returns>SystemController instance</returns>
public static SystemController GetInstance()
{
// Create a new instance if non exists
if (instance == null)
{
instance = new SystemController();
instance.sceneStack.Push(SceneManager.GetActiveScene().buildIndex);
}
return instance;
}
/// <summary>
/// Load the scene and push on the stack
/// </summary>
/// <param name="scenePath">Path of the scene</param>
public void LoadNextScene(string scenePath)
{
LoadNextScene(SceneUtility.GetBuildIndexByScenePath(scenePath));
}
/// <summary>
/// Load the scene and push on the stack
/// </summary>
/// <param name="sceneIndex">Buildindex of the scene</param>
public void LoadNextScene(int sceneIndex)
{
sceneStack.Push(sceneIndex);
SceneManager.LoadScene(sceneIndex);
}
/// <summary>
/// Swap the current scene with the new scene on the stack
/// </summary>
/// <param name="scenePath">Path of the scene</param>
public void SwapScene(string scenePath)
{
SwapScene(SceneUtility.GetBuildIndexByScenePath(scenePath));
}
/// <summary>
/// Swap the current scene with the new scene on the stack
/// </summary>
/// <param name="sceneIndex">Buildindex of the scene</param>
public void SwapScene(int sceneIndex)
{
sceneStack.Pop();
LoadNextScene(sceneIndex);
}
/// <summary>
/// Go back to the previous scene and unload the current scene
/// </summary>
public void BackToPreviousScene()
{
sceneStack.Pop();
if (sceneStack.Count > 0) SceneManager.LoadScene(sceneStack.Peek());
else Application.Quit();
}
/// <summary>
/// Go back to a specific scene, unloading all the scenes on the way
/// </summary>
/// <param name="scenePath">Path of the scene</param>
public void BackToScene(string scenePath)
{
BackToScene(SceneUtility.GetBuildIndexByScenePath(scenePath));
}
/// <summary>
/// Go back to a specific scene, unloading all the scene on the way
/// </summary>
/// <param name="sceneIndex">Buildindex of the scene</param>
public void BackToScene(int sceneIndex)
{
while (0 < sceneStack.Count && sceneStack.Peek() != sceneIndex) sceneStack.Pop();
if (sceneStack.Count > 0) SceneManager.LoadScene(sceneStack.Peek());
else Application.Quit();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf5ea73aa43049e45a0ad926db15f315
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
/// <summary>
/// Enum for easy indexing and checking if a course is of a certain kind
/// </summary>
public enum ThemeIndex
{
SIGN_ALPHABET,
SIGN_CLOTHING,
SIGN_ANIMALS,
SIGN_FOOD,
SIGN_HOBBIES,
SIGN_HOUSE,
SIGN_FAMILY,
SPELLING_GEOGRAPY,
SPELLING_BUILDINGS,
SPELLING_SPORTS,
SPELLING_BASICS,
SPELLING_HOBBIES,
SPELLING_PEOPLE,
SPELLING_FRUIT,
SPELLING_VEGGIES,
SPELLING_WILD,
SPELLING_FARM
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58e9afcf842bdfa48939e754bb39182a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8559f509b8f924f44bc10e2d20ac3eed
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,606 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
[TestFixture]
public class PersistentDataTests
{
/// <summary>
/// Create a new path so the existing .json file will not be overwritten
/// </summary>
private static string PATH = $"{Application.persistentDataPath}/wesign_unit_test.json";
/// <summary>
/// Reference to the pdc to perform tests on
/// </summary>
private PersistentDataController pdc = null;
/// <summary>
/// A dummy serializable struct to perform test operations on
/// </summary>
[Serializable]
private struct Struct
{
public int r, g, b;
public float x, y, z;
}
/// <summary>
/// A dummy serializable enum to perform test operations on
/// </summary>
private enum Enum
{
SQUARE,
TRIANBLE,
CIRCLE
}
[SetUp]
public void Setup_PersistentDataController()
{
PersistentDataController.PATH = PersistentDataTests.PATH;
//PersistentDataController.PATH = null;
pdc = PersistentDataController.GetInstance();
}
[Test]
public void Test_PersistentDataController_GetInstance()
{
Assert.IsNotNull(pdc);
//Assert.AreEqual($"{Application.persistentDataPath}/wesign_saved_data.json", PersistentDataController.PATH);
}
[Test]
public void Test_PersistentDataController_Clear()
{
pdc.Load();
pdc.Clear();
Assert.Zero(pdc.GetUsers().Count);
Assert.AreEqual(-1, pdc.GetCurrentUser());
}
[Test]
public void Test_PersistentDataController_Save_Empty()
{
pdc.Load();
pdc.Clear();
pdc.Save();
FileAssert.Exists(PATH);
string content = File.ReadAllText(PATH);
string expected = $"{{\"version\":{PersistentDataController.VERSION},\"users\":[],\"currentUser\":-1,\"currentMinigame\":0,\"currentCourse\":0,\"currentTheme\":0}}";
Assert.AreEqual(expected, content);
}
[Test]
public void Test_PersistentDataController_Save_New()
{
pdc.Load();
pdc.Clear();
if (File.Exists(PATH))
File.Delete(PATH);
pdc.Save();
FileAssert.Exists(PATH);
string content = File.ReadAllText(PATH);
string expected = $"{{\"version\":{PersistentDataController.VERSION},\"users\":[],\"currentUser\":-1,\"currentMinigame\":0,\"currentCourse\":0,\"currentTheme\":0}}";
Assert.AreEqual(expected, content);
}
[Test]
public void Test_PersistentDataController_Load_Existing()
{
string content = $"{{\"version\":{PersistentDataController.VERSION},\"users\":[],\"currentUser\":-1,\"currentMinigame\":0,\"currentCourse\":0,\"currentTheme\":0}}";
File.WriteAllText(PATH, content);
Assert.IsTrue(pdc.Load(false));
}
[Test]
public void Test_PersistentDataController_Load_OlderVersion()
{
string content = $"{{\"version\":{PersistentDataController.VERSION - 1},\"users\":[],\"currentUser\":-1,\"currentMinigame\":0,\"currentCourse\":0,\"currentTheme\":0}}";
File.WriteAllText(PATH, content);
Assert.IsFalse(pdc.Load(false));
}
[Test]
public void Test_PersistentDataController_Load_New()
{
if (File.Exists(PATH))
File.Delete(PATH);
Assert.IsFalse(pdc.Load(false));
FileAssert.DoesNotExist(PATH);
}
[Test]
public void Test_PersistentDataController_Load_Exception()
{
File.WriteAllText(PATH, "https://www.youtube.com/watch?v=dQw4w9WgXcQ");
Assert.IsFalse(pdc.Load(false));
Assert.AreEqual("https://www.youtube.com/watch?v=dQw4w9WgXcQ", File.ReadAllText(PATH));
}
[Test]
public void Test_PersistentDataController_Load_Override()
{
File.WriteAllText(PATH, "https://www.youtube.com/watch?v=dQw4w9WgXcQ");
Assert.IsFalse(pdc.Load(true));
string content = File.ReadAllText(PATH);
string expected = $"{{\"version\":{PersistentDataController.VERSION},\"users\":[],\"currentUser\":-1,\"currentMinigame\":0,\"currentCourse\":0,\"currentTheme\":0}}";
Assert.AreEqual(expected, content);
}
[Test]
public void Test_PersistentDataController_Version()
{
const int VERSION = 0x04_01;
Assert.AreEqual(VERSION, PersistentDataController.VERSION);
}
[Test]
public void Test_PersistentDataController_AddUser()
{
pdc.Load();
pdc.Clear();
var d = new PersistentDataController.SavedUserData()
{
username = "username",
avatarIndex = 0
};
pdc.AddUser(d);
string content = File.ReadAllText(PATH);
string expected = $"{{\"version\":{PersistentDataController.VERSION},\"users\":[{{\"entries\":[],\"username\":\"username\",\"avatarIndex\":0,\"playtime\":0.0,\"minigames\":[],\"courses\":[]}}],\"currentUser\":0,\"currentMinigame\":0,\"currentCourse\":0,\"currentTheme\":0}}";
Assert.AreEqual(expected, content);
}
[Test]
public void Test_PersistentDataController_GetUsers()
{
pdc.Load();
pdc.Clear();
var d = new PersistentDataController.SavedUserData()
{
username = "username",
avatarIndex = 0
};
pdc.AddUser(d);
var users = pdc.GetUsers();
Assert.AreEqual(1, users.Count);
Assert.AreEqual("username", users[0].username);
Assert.AreEqual(0, users[0].avatarIndex);
}
[Test]
public void Test_PersistentDataController_GetCurrentUser()
{
pdc.Load();
pdc.Clear();
Assert.AreEqual(-1, pdc.GetCurrentUser());
pdc.AddUser(new PersistentDataController.SavedUserData()
{
username = "username",
avatarIndex = 0
});
Assert.AreEqual(0, pdc.GetCurrentUser());
}
[Test]
public void Test_PersistentDataController_SetCurrentUser()
{
pdc.Load();
pdc.Clear();
for (int i = 0; i < 5; i++)
pdc.AddUser(new PersistentDataController.SavedUserData()
{
username = $"username_{i}",
avatarIndex = i
});
pdc.SetCurrentUser(3);
Assert.AreEqual(3, pdc.GetCurrentUser());
}
[Test]
public void Test_PersistentDataController_SetCurrentUser_Invalid()
{
pdc.Load();
pdc.Clear();
pdc.AddUser(new PersistentDataController.SavedUserData()
{
username = $"username",
avatarIndex = 0
});
Assert.Throws<IndexOutOfRangeException>(delegate { pdc.SetCurrentUser(3); });
}
[Test]
public void Test_PersistentDataController_SetCurrentUser_Empty()
{
pdc.Load();
pdc.Clear();
Assert.Throws<IndexOutOfRangeException>(delegate { pdc.SetCurrentUser(0); });
}
[Test]
public void Test_PersistentDataController_DeleteUser_BeforeCurrent()
{
pdc.Load();
pdc.Clear();
var users = new List<PersistentDataController.SavedUserData>();
for (int i = 0; i < 5; i++)
{
var d = new PersistentDataController.SavedUserData()
{
username = $"username_{i}",
avatarIndex = i
};
pdc.AddUser(d);
users.Add(d);
}
pdc.SetCurrentUser(0);
users.RemoveAt(2);
pdc.DeleteUser(2);
var vsers = pdc.GetUsers();
Assert.AreEqual(0, pdc.GetCurrentUser());
Assert.AreEqual(users.Count, vsers.Count);
for (int i = 0; i < 4; i++)
{
Assert.AreEqual(users[i].username, vsers[i].username);
Assert.AreEqual(users[i].avatarIndex, vsers[i].avatarIndex);
}
}
[Test]
public void Test_PersistentDataController_DeleteUser_Current()
{
pdc.Load();
pdc.Clear();
var users = new List<PersistentDataController.SavedUserData>();
for (int i = 0; i < 5; i++)
{
var d = new PersistentDataController.SavedUserData()
{
username = $"username_{i}",
avatarIndex = i
};
pdc.AddUser(d);
users.Add(d);
}
pdc.SetCurrentUser(2);
users.RemoveAt(2);
pdc.DeleteUser(2);
var vsers = pdc.GetUsers();
Assert.AreEqual(1, pdc.GetCurrentUser());
Assert.AreEqual(users.Count, vsers.Count);
for (int i = 0; i < 4; i++)
{
Assert.AreEqual(users[i].username, vsers[i].username);
Assert.AreEqual(users[i].avatarIndex, vsers[i].avatarIndex);
}
}
[Test]
public void Test_PersistentDataController_DeleteUser_AfterCurrent()
{
pdc.Load();
pdc.Clear();
var users = new List<PersistentDataController.SavedUserData>();
for (int i = 0; i < 5; i++)
{
var d = new PersistentDataController.SavedUserData()
{
username = $"username_{i}",
avatarIndex = i
};
pdc.AddUser(d);
users.Add(d);
}
pdc.SetCurrentUser(4);
users.RemoveAt(2);
pdc.DeleteUser(2);
var vsers = pdc.GetUsers();
Assert.AreEqual(3, pdc.GetCurrentUser());
Assert.AreEqual(users.Count, vsers.Count);
for (int i = 0; i < 4; i++)
{
Assert.AreEqual(users[i].username, vsers[i].username);
Assert.AreEqual(users[i].avatarIndex, vsers[i].avatarIndex);
}
}
[Test]
public void Test_PersistentDataController_DeleteUser_Invalid()
{
pdc.Load();
pdc.Clear();
pdc.AddUser(new PersistentDataController.SavedUserData()
{
username = $"username",
avatarIndex = 0
});
Assert.Throws<IndexOutOfRangeException>(delegate { pdc.SetCurrentUser(3); });
}
[Test]
public void Test_PersistentDataController_DeleteUser_Empty()
{
pdc.Load();
pdc.Clear();
Assert.Throws<IndexOutOfRangeException>(delegate { pdc.DeleteUser(0); });
}
[Test]
public void Test_PersistentDataController_CurrentCourse()
{
pdc.Load();
pdc.Clear();
pdc.SetCurrentCourse(CourseIndex.FINGERSPELLING);
Assert.AreEqual(CourseIndex.FINGERSPELLING, pdc.GetCurrentCourse());
}
[Test]
public void Test_PersistentDataController_CurrentMinigame()
{
pdc.Load();
pdc.Clear();
pdc.SetCurrentMinigame(MinigameIndex.SPELLING_BEE);
Assert.AreEqual(MinigameIndex.SPELLING_BEE, pdc.GetCurrentMinigame());
}
[Test]
public void Test_PersistentDataController_CurrentTheme()
{
pdc.Load();
pdc.Clear();
pdc.SetCurrentTheme(ThemeIndex.SIGN_ALPHABET);
Assert.AreEqual(ThemeIndex.SIGN_ALPHABET, pdc.GetCurrentTheme());
}
[Test]
public void Test_New_PersistentDataContainer()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsNotNull(c);
Assert.Zero(c.entries.Count);
}
[Test]
public void Test_PersistentDataContainer_Set_Invalid()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsFalse(c.Set<object>("key", null));
}
[Test]
public void Test_PersistentDataContainer_Set_DuplicateKey()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsTrue(c.Set<int>("key", 123));
Assert.IsTrue(c.Set<int>("key", 321));
}
[Test]
public void Test_PersistentDataContainer_Set_Int()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsTrue(c.Set<int>("key", 123));
}
[Test]
public void Test_PersistentDataContainer_Set_String()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsTrue(c.Set<string>("key", "abc"));
}
[Test]
public void Test_PersistentDataContainer_Set_Struct()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsTrue(c.Set<Struct>("key", new Struct()));
}
[Test]
public void Test_PersistentDataContainer_Set_Enum()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsTrue(c.Set<Enum>("key", new Enum()));
}
[Test]
public void Test_PersistentDataContainer_Get_InvalidType()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<Struct>("key", new Struct() { r = 255, g = 127, b = 63, x = 31, y = 15, z = 7 });
Assert.Throws<InvalidCastException>(delegate { c.Get<int>("key"); });
c.Set<int>("key", 123);
Assert.Throws<InvalidCastException>(delegate { c.Get<Struct>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Get_KeyNotFound()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<int>("key", 123);
Assert.Throws<KeyNotFoundException>(delegate { c.Get<int>("KEY"); });
}
[Test]
public void Test_PersistentDataContainer_Get_Empty()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.Throws<KeyNotFoundException>(delegate { c.Get<int>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Get_Int()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<int>("key", 123);
Assert.AreEqual(123, c.Get<int>("key"));
}
[Test]
public void Test_PersistentDataContainer_Get_String()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<string>("key", "value");
Assert.AreEqual("value", c.Get<string>("key"));
}
[Test]
public void Test_PersistentDataContainer_Get_Struct()
{
var c = new PersistentDataController.PersistentDataContainer();
var s = new Struct() { r = 255, g = 127, b = 63, x = 31, y = 15, z = 7 };
c.Set<Struct>("key", s);
Assert.AreEqual(s, c.Get<Struct>("key"));
}
[Test]
public void Test_PersistentDataContainer_Get_Enum()
{
var c = new PersistentDataController.PersistentDataContainer();
var e = Enum.CIRCLE;
c.Set<Enum>("key", e);
Assert.AreEqual(e, c.Get<Enum>("key"));
}
[Test]
public void Test_PersistentDataContainer_Remove_Invalid()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<int>("key", 123);
Assert.Throws<KeyNotFoundException>(delegate { c.Remove("KEY"); });
}
[Test]
public void Test_PersistentDataContainer_Remove_Empty()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.Throws<KeyNotFoundException>(delegate { c.Remove("key"); });
}
[Test]
public void Test_PersistentDataContainer_Remove_Int()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<int>("key", 123);
c.Remove("key");
Assert.Throws<KeyNotFoundException>(delegate { c.Get<int>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Remove_String()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<string>("key", "value");
c.Remove("key");
Assert.Throws<KeyNotFoundException>(delegate { c.Get<string>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Remove_Struct()
{
var c = new PersistentDataController.PersistentDataContainer();
var s = new Struct() { r = 255, g = 127, b = 63, x = 31, y = 15, z = 7 };
c.Set<Struct>("key", s);
c.Remove("key");
Assert.Throws<KeyNotFoundException>(delegate { c.Get<Struct>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Remove_Enum()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<Enum>("key", Enum.CIRCLE);
c.Remove("key");
Assert.Throws<KeyNotFoundException>(delegate { c.Get<Enum>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Pop_Invalid()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<int>("key", 123);
Assert.Throws<KeyNotFoundException>(delegate { c.Remove("KEY"); });
}
[Test]
public void Test_PersistentDataContainer_Pop_Empty()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.Throws<KeyNotFoundException>(delegate { c.Remove("KEY"); });
}
[Test]
public void Test_PersistentDataContainer_Pop_Int()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<int>("key", 123);
Assert.AreEqual(123, c.Pop<int>("key"));
Assert.Throws<KeyNotFoundException>(delegate { c.Get<int>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Pop_String()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<string>("key", "value");
Assert.AreEqual("value", c.Pop<string>("key"));
Assert.Throws<KeyNotFoundException>(delegate { c.Get<string>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Pop_Struct()
{
var c = new PersistentDataController.PersistentDataContainer();
var s = new Struct() { r = 255, g = 127, b = 63, x = 31, y = 15, z = 7 };
c.Set<Struct>("key", s);
Assert.AreEqual(s, c.Pop<Struct>("key"));
Assert.Throws<KeyNotFoundException>(delegate { c.Get<Struct>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Pop_Enum()
{
var c = new PersistentDataController.PersistentDataContainer();
c.Set<Enum>("key", Enum.CIRCLE);
Assert.AreEqual(Enum.CIRCLE, c.Pop<Enum>("key"));
Assert.Throws<KeyNotFoundException>(delegate { c.Get<Enum>("key"); });
}
[Test]
public void Test_PersistentDataContainer_Has_ValidKey()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsFalse(c.Has("key"));
c.Set<int>("key", 123);
Assert.IsTrue(c.Has("key"));
}
[Test]
public void Test_PersistentDataContainer_Has_InvalidKey()
{
var c = new PersistentDataController.PersistentDataContainer();
Assert.IsFalse(c.Has("KEY"));
c.Set<int>("key", 123);
Assert.IsFalse(c.Has("KEY"));
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56873c5649b881846a54e2a2aa5ce499
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
{
"name": "SystemArchitectureTests",
"rootNamespace": "",
"references": [
"UnityEditor.TestRunner",
"UnityEngine.TestRunner",
"SystemArchitecture"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b1a4ef95cbacdca459433eb2ddc05755
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: