Initial commit
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "BracerLib",
|
||||
"rootNamespace": "BracerLib",
|
||||
"references": [
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a60bafe2eaba2f24fbb33ab1ab52999b
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e57509c8f334614782377c50d3b006b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Utility.Coroutines
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class CancellableWaitForSeconds : CustomYieldInstruction
|
||||
{
|
||||
private float remaining;
|
||||
private readonly Func<bool> predicate;
|
||||
|
||||
public override bool keepWaiting => ShouldKeepWaiting();
|
||||
|
||||
public CancellableWaitForSeconds(float wait, Func<bool> predicate)
|
||||
{
|
||||
remaining = wait;
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
private bool ShouldKeepWaiting()
|
||||
{
|
||||
remaining -= Time.deltaTime;
|
||||
var check = CheckCustomPredicate();
|
||||
|
||||
return !(check || remaining < 0f);
|
||||
}
|
||||
|
||||
private bool CheckCustomPredicate() => predicate?.Invoke() ?? false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1f806820d3cd904399807656f490d62
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 095d1626aba91364faf64598b4f35f64
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace BracerLib.DI
|
||||
{
|
||||
public struct Dependency
|
||||
{
|
||||
public Type Type { get; }
|
||||
public FactoryFunc Factory { get; }
|
||||
public DependencyLifetime Lifetime { get; }
|
||||
|
||||
public Dependency(Type type, FactoryFunc provider, DependencyLifetime lifetime)
|
||||
{
|
||||
Type = type;
|
||||
Factory = provider;
|
||||
Lifetime = lifetime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08b41999af4aec5459aa82b9a53bcf46
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BracerLib.DI
|
||||
{
|
||||
public class DependencyCollection : IEnumerable<Dependency>
|
||||
{
|
||||
private readonly IList<Dependency> dependencies = new List<Dependency>();
|
||||
|
||||
public void Add(Dependency dependency) => dependencies.Add(dependency);
|
||||
|
||||
public IEnumerator<Dependency> GetEnumerator() => dependencies.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => dependencies.GetEnumerator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a09894721d4f3714aa39bc4335147866
|
||||
@@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BracerLib.DI
|
||||
{
|
||||
[DefaultExecutionOrder(-1)]
|
||||
public abstract class DependencyContext : MonoBehaviour
|
||||
{
|
||||
private DependencyProvider dependencyProvider;
|
||||
protected readonly DependencyCollection dependencyCollection = new();
|
||||
|
||||
protected abstract void Setup();
|
||||
|
||||
protected abstract void Configure();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Setup();
|
||||
|
||||
dependencyProvider = new DependencyProvider(dependencyCollection);
|
||||
|
||||
var children = GetComponentsInChildren<MonoBehaviour>(true);
|
||||
foreach (var child in children)
|
||||
dependencyProvider.Inject(child);
|
||||
|
||||
Configure();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af6df49e6c26ffe41a2c34e13afdb851
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace BracerLib.DI
|
||||
{
|
||||
public delegate object FactoryFunc(DependencyProvider provider);
|
||||
|
||||
public static class DependencyFactory
|
||||
{
|
||||
public static FactoryFunc FromClass<T>() where T : class, new()
|
||||
{
|
||||
return provider =>
|
||||
{
|
||||
var type = typeof(T);
|
||||
var obj = FormatterServices.GetUninitializedObject(type);
|
||||
|
||||
provider.Inject(obj);
|
||||
|
||||
type.GetConstructor(Type.EmptyTypes)?.Invoke(obj, null);
|
||||
|
||||
return (T)obj;
|
||||
};
|
||||
}
|
||||
|
||||
public static FactoryFunc FromGameObject<T>(T instance) where T : MonoBehaviour
|
||||
{
|
||||
return dependencies =>
|
||||
{
|
||||
var children = instance.GetComponentsInChildren<MonoBehaviour>(true);
|
||||
foreach (var child in children)
|
||||
dependencies.Inject(child);
|
||||
|
||||
return instance;
|
||||
};
|
||||
}
|
||||
|
||||
public static FactoryFunc FromPrefab<T>(T prefab) where T : MonoBehaviour
|
||||
{
|
||||
return provider =>
|
||||
{
|
||||
var prefabObj = prefab.gameObject;
|
||||
var wasActive = prefabObj.activeSelf;
|
||||
prefabObj.SetActive(false);
|
||||
var instance = Object.Instantiate(prefab);
|
||||
prefabObj.SetActive(wasActive);
|
||||
var children = instance.GetComponentsInChildren<MonoBehaviour>(true);
|
||||
|
||||
foreach (var child in children)
|
||||
provider.Inject(child);
|
||||
|
||||
instance.gameObject.SetActive(wasActive);
|
||||
|
||||
return instance.GetComponent<T>();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4dd8b23ba952434428cdbc308db727b1
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BracerLib.DI
|
||||
{
|
||||
public enum DependencyLifetime
|
||||
{
|
||||
Transient = 0,
|
||||
Singleton
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d180c5acba365c4c9381bc61d6c6781
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BracerLib.DI
|
||||
{
|
||||
public class DependencyProvider
|
||||
{
|
||||
private IDictionary<Type, Dependency> dependencyMap = new Dictionary<Type, Dependency>();
|
||||
private IDictionary<Type, object> singletons = new Dictionary<Type, object>();
|
||||
|
||||
public DependencyProvider(DependencyCollection collection)
|
||||
{
|
||||
foreach (var d in collection)
|
||||
dependencyMap.Add(d.Type, d);
|
||||
}
|
||||
|
||||
public object Get(Type type)
|
||||
{
|
||||
if (!dependencyMap.ContainsKey(type))
|
||||
throw new ArgumentException($"Type is not a dependency: {type.FullName}");
|
||||
|
||||
var dependency = dependencyMap[type];
|
||||
switch (dependency.Lifetime)
|
||||
{
|
||||
case DependencyLifetime.Transient: return dependency.Factory(this);
|
||||
case DependencyLifetime.Singleton:
|
||||
if (!singletons.ContainsKey(type))
|
||||
singletons.Add(type, dependency.Factory(this));
|
||||
|
||||
return singletons[type];
|
||||
default: throw new ArgumentOutOfRangeException($"Unavailable lifetime for dependency: {type.FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
public T Get<T>() => (T)Get(typeof(T));
|
||||
|
||||
public object Inject(object dependant)
|
||||
{
|
||||
var type = dependant.GetType();
|
||||
while (type != null)
|
||||
{
|
||||
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly |
|
||||
BindingFlags.Instance);
|
||||
foreach (var f in fields)
|
||||
{
|
||||
if (f.GetCustomAttribute<InjectFieldAttribute>(false) == null)
|
||||
continue;
|
||||
|
||||
f.SetValue(dependant, Get(f.FieldType));
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
return dependant;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0857e79315720b44eb82767785bfa5b4
|
||||
@@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace BracerLib.DI
|
||||
{
|
||||
public class InjectFieldAttribute : Attribute { }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f10c710f6a498a468c7c3d5a3f7f429
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3050982b0392a94aa176a80fc5a54e1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Data
|
||||
{
|
||||
[Serializable, ExcludeFromCoverage]
|
||||
public struct Layer : IEquatable<Layer>
|
||||
{
|
||||
public static implicit operator int(Layer layer)
|
||||
{
|
||||
return layer.layerIndex;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private int layerIndex;
|
||||
|
||||
public int LayerIndex
|
||||
{
|
||||
get => layerIndex;
|
||||
set
|
||||
{
|
||||
if (value > 0 && value < 32)
|
||||
layerIndex = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Mask => 1 << layerIndex;
|
||||
|
||||
public bool Equals(Layer other) => layerIndex == other.layerIndex;
|
||||
|
||||
public override bool Equals(object obj) => obj is Layer other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => layerIndex;
|
||||
|
||||
public static bool operator ==(Layer left, Layer right) => left.Equals(right);
|
||||
|
||||
public static bool operator !=(Layer left, Layer right) => !left.Equals(right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c4f8fe70c14aea4ea15d53ddb93f727
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 777c7569a062abb4cbf6f9bd72906506
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "BracerLib.Editor",
|
||||
"rootNamespace": "BracerLib.Editor",
|
||||
"references": [
|
||||
"GUID:a60bafe2eaba2f24fbb33ab1ab52999b"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed2fb3312eb6acc4e90f5b6ac5964596
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d3660d7d1878fd4dab8b7a904c7a0be
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
using BracerLib.Data;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Editor.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Referenced via: https://discussions.unity.com/t/select-only-one-layer-in-the-inspector-select-only-one-layer-in-the-inspector/230727
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(Layer))]
|
||||
[ExcludeFromCoverage]
|
||||
public class LayerPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, GUIContent.none, property);
|
||||
var layerIndex = property.FindPropertyRelative("layerIndex");
|
||||
|
||||
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
|
||||
if (layerIndex != null)
|
||||
layerIndex.intValue = EditorGUI.LayerField(position, layerIndex.intValue);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b603be9d2db54a6ba8f16f73927f322c
|
||||
timeCreated: 1773804726
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aebda41daf072e0438978241a08410d4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using BracerLib.UI;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UI;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Editor.UI
|
||||
{
|
||||
//Don't forget to put this file inside a 'Editor' folder
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(NonDrawingGraphic), false)]
|
||||
[ExcludeFromCoverage]
|
||||
public class NonDrawingGraphicEditor : GraphicEditor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.PropertyField(m_Script, Array.Empty<GUILayoutOption>());
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
// skipping AppearanceControlsGUI
|
||||
RaycastControlsGUI();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bea4dc700684cb5953636c68a1543e7
|
||||
timeCreated: 1686367053
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfe5690b14e7536429ffc5eee664df2c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Editor.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Referenced via https://github.com/marijnz/unity-autocomplete-search-field
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[Obsolete("Use Odin or some attribute-based item instead.")]
|
||||
[ExcludeFromCoverage]
|
||||
public class EditorSearchField
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public const float RESULT_HEIGHT = 20f;
|
||||
public const float RESULT_BORDER_WIDTH = 2f;
|
||||
public const float RESULT_MARGIN = 15f;
|
||||
public const float RESULT_LABEL_OFFSET = 2f;
|
||||
|
||||
public static readonly GUIStyle ENTRY_EVEN;
|
||||
public static readonly GUIStyle ENTRY_ODD;
|
||||
public static readonly GUIStyle LABEL_STYLE;
|
||||
public static readonly GUIStyle RESULTS_BORDER_STYLE;
|
||||
|
||||
static Styles()
|
||||
{
|
||||
ENTRY_EVEN = new GUIStyle("CN EntryBackEven");
|
||||
ENTRY_ODD = new GUIStyle("CN EntryBackOdd");
|
||||
RESULTS_BORDER_STYLE = new GUIStyle("hostview");
|
||||
|
||||
LABEL_STYLE = new GUIStyle
|
||||
{
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
richText = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void RepaintFocusedWindow()
|
||||
{
|
||||
if (EditorWindow.focusedWindow != null)
|
||||
EditorWindow.focusedWindow.Repaint();
|
||||
}
|
||||
|
||||
private SearchField searchField;
|
||||
private string searchString;
|
||||
|
||||
public event Action<string> OnInputChanged;
|
||||
|
||||
public bool HasSearchString => !string.IsNullOrEmpty(SearchString);
|
||||
public string SearchString
|
||||
{
|
||||
get => searchString;
|
||||
set
|
||||
{
|
||||
searchString = value;
|
||||
OnGUI();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
var rect = GUILayoutUtility.GetRect(1, 1, 18, 18, GUILayout.ExpandWidth(true));
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
DoSearchField(rect);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DoSearchField(Rect rect)
|
||||
{
|
||||
if (searchField == null)
|
||||
searchField = new SearchField();
|
||||
|
||||
var result = searchField.OnGUI(rect, searchString);
|
||||
if (result != searchString)
|
||||
OnInputChanged?.Invoke(result);
|
||||
|
||||
searchString = result;
|
||||
|
||||
if (HasSearchbarFocused())
|
||||
RepaintFocusedWindow();
|
||||
}
|
||||
|
||||
private bool HasSearchbarFocused() => GUIUtility.keyboardControl == searchField.searchFieldControlID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0320c64c0c142a999d375385c65efee
|
||||
timeCreated: 1727491490
|
||||
@@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Editor.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// A set-up for a component that can be a searchable list. Deprecated but fun to create.
|
||||
/// Doesn't work for entry adds.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[ExcludeFromCoverage]
|
||||
[Obsolete]
|
||||
public class SearchableScrollView<T>
|
||||
{
|
||||
public delegate void DataDelegate(int index, string originalValue, string newValue);
|
||||
|
||||
public delegate bool ValidationDelegate(string entry);
|
||||
|
||||
private static class Styles
|
||||
{
|
||||
public static readonly GUIStyle SELECTED;
|
||||
|
||||
static Styles()
|
||||
{
|
||||
var t = new Texture2D(1, 1);
|
||||
t.SetPixel(0, 0, new Color(0.4f, 0.4f, 0.4f));
|
||||
t.Apply();
|
||||
|
||||
SELECTED = new GUIStyle(EditorStyles.label);
|
||||
SELECTED.normal.textColor = Color.white;
|
||||
SELECTED.normal.background = t;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string EDIT_FIELD = "selectedTextField";
|
||||
|
||||
private readonly EditorSearchField searchField;
|
||||
private IList<T> data;
|
||||
private IList<string> dataLabels = new List<string>();
|
||||
private IList<string> searchResults;
|
||||
private readonly IDictionary<int, int> filteredLabelsToData = new Dictionary<int, int>();
|
||||
private EventType prevEventType;
|
||||
private int selectedIndex = -1;
|
||||
private bool isEditing;
|
||||
private bool isNewEntry;
|
||||
private bool cancelEdit;
|
||||
private bool editingFinished;
|
||||
private string originalEntryText;
|
||||
private string editedEntryText;
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
public float MaxWidth { get; set; }
|
||||
public IList<T> Data
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
data = value;
|
||||
|
||||
searchField.SearchString = string.Empty;
|
||||
|
||||
dataLabels.Clear();
|
||||
dataLabels = data.Select(d => d.ToString()).ToList();
|
||||
|
||||
filteredLabelsToData.Clear();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The modified index of the selection in the list with the search results taken into account.
|
||||
/// </summary>
|
||||
public int SelectedIndex => filteredLabelsToData.ContainsKey(selectedIndex) ? filteredLabelsToData[selectedIndex] : selectedIndex;
|
||||
public ValidationDelegate ValidateEntry { get; set; }
|
||||
public DataDelegate DataEntryAdded { get; set; }
|
||||
public DataDelegate DataEntryEdited { get; set; }
|
||||
public DataDelegate DataEntryRemoved { get; set; }
|
||||
|
||||
private IList<string> TargetData => (searchResults?.Count ?? 0) > 0 ? searchResults : dataLabels;
|
||||
|
||||
public SearchableScrollView()
|
||||
{
|
||||
searchField = new EditorSearchField();
|
||||
searchField.OnInputChanged += SearchFieldInputChanged;
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
HandleEditing();
|
||||
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
||||
searchField.OnGUI();
|
||||
|
||||
scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, GUILayout.MaxWidth(MaxWidth));
|
||||
GUILayout.BeginVertical();
|
||||
|
||||
// Output all keys in the list, with some being modified in style or interact-ability by way of flags
|
||||
var newEntryIndex = -1;
|
||||
var targetData = TargetData;
|
||||
for (var i = 0; i < (targetData?.Count ?? 0); i++)
|
||||
{
|
||||
var target = targetData![i];
|
||||
var selected = selectedIndex == i;
|
||||
|
||||
if (isEditing && selected)
|
||||
{
|
||||
if (originalEntryText == null)
|
||||
{
|
||||
originalEntryText = target;
|
||||
editedEntryText = target;
|
||||
}
|
||||
|
||||
GUI.SetNextControlName(EDIT_FIELD);
|
||||
editedEntryText = GUILayout.TextField(editedEntryText, Styles.SELECTED);
|
||||
GUI.FocusControl(EDIT_FIELD);
|
||||
}
|
||||
else if (cancelEdit && selected)
|
||||
{
|
||||
cancelEdit = false;
|
||||
|
||||
if (isNewEntry)
|
||||
newEntryIndex = i;
|
||||
else
|
||||
targetData[i] = originalEntryText;
|
||||
|
||||
originalEntryText = null;
|
||||
editedEntryText = null;
|
||||
}
|
||||
else if (editingFinished && selected)
|
||||
{
|
||||
editingFinished = false;
|
||||
UpdateListValue(i, originalEntryText, editedEntryText);
|
||||
|
||||
originalEntryText = null;
|
||||
editedEntryText = null;
|
||||
}
|
||||
else if (GUILayout.Button(target, selected ? Styles.SELECTED : EditorStyles.label) && !isEditing)
|
||||
selectedIndex = selected ? -1 : i;
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.Label("F2 = Edit entry\nEnter = Accept and apply changes\nEscape = Cancel entry edit", EditorStyles.helpBox);
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
public void AddNewEntry()
|
||||
{
|
||||
searchField.SearchString = string.Empty;
|
||||
dataLabels.Add(string.Empty);
|
||||
|
||||
selectedIndex = dataLabels.Count - 1;
|
||||
isEditing = true;
|
||||
isNewEntry = true;
|
||||
}
|
||||
|
||||
public void DeleteEntry()
|
||||
{
|
||||
if (selectedIndex < 0)
|
||||
return;
|
||||
|
||||
dataLabels.RemoveAt(SelectedIndex);
|
||||
|
||||
DataEntryRemoved?.Invoke(SelectedIndex, null, null);
|
||||
|
||||
searchField.SearchString = string.Empty;
|
||||
}
|
||||
|
||||
private void FinalizeNewEntry()
|
||||
{
|
||||
if (!isEditing)
|
||||
return;
|
||||
|
||||
isEditing = false;
|
||||
editingFinished = true;
|
||||
}
|
||||
|
||||
private void HandleEditing()
|
||||
{
|
||||
var eventType = Event.current.type;
|
||||
var isKeyDown = eventType == EventType.KeyDown;
|
||||
if (selectedIndex < 0 || !isKeyDown || eventType == prevEventType)
|
||||
{
|
||||
if (!isKeyDown)
|
||||
prevEventType = eventType;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
prevEventType = eventType;
|
||||
|
||||
var keyCode = Event.current.keyCode;
|
||||
switch (keyCode)
|
||||
{
|
||||
case KeyCode.Escape:
|
||||
if (!isEditing)
|
||||
return;
|
||||
|
||||
isEditing = false;
|
||||
cancelEdit = true;
|
||||
|
||||
break;
|
||||
case KeyCode.Return:
|
||||
case KeyCode.KeypadEnter:
|
||||
FinalizeNewEntry();
|
||||
|
||||
break;
|
||||
case KeyCode.F2:
|
||||
isEditing = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateListValue(int index, string origText, string editedText)
|
||||
{
|
||||
if (editedText == origText || (ValidateEntry != null && !ValidateEntry(editedText)))
|
||||
{
|
||||
if (isNewEntry)
|
||||
{
|
||||
isNewEntry = false;
|
||||
|
||||
dataLabels.RemoveAt(dataLabels.Count - 1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (filteredLabelsToData.TryGetValue(index, out var origIndex))
|
||||
{
|
||||
searchResults[index] = editedText;
|
||||
dataLabels[origIndex] = editedText;
|
||||
|
||||
index = origIndex;
|
||||
}
|
||||
else
|
||||
dataLabels[index] = editedText;
|
||||
|
||||
if (isNewEntry)
|
||||
{
|
||||
isNewEntry = false;
|
||||
DataEntryAdded?.Invoke(index, null, editedText);
|
||||
}
|
||||
else
|
||||
DataEntryEdited?.Invoke(index, origText, editedText);
|
||||
}
|
||||
|
||||
private void SearchFieldInputChanged(string searchString)
|
||||
{
|
||||
searchResults ??= new List<string>();
|
||||
|
||||
searchResults.Clear();
|
||||
filteredLabelsToData.Clear();
|
||||
|
||||
if (searchString != null)
|
||||
{
|
||||
var newIndex = 0;
|
||||
searchResults = dataLabels.Where((label, index) =>
|
||||
{
|
||||
var result = label.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
|
||||
if (result)
|
||||
filteredLabelsToData.Add(newIndex++, index);
|
||||
|
||||
return result;
|
||||
}).ToList();
|
||||
}
|
||||
else
|
||||
searchResults = dataLabels;
|
||||
|
||||
if (selectedIndex >= searchResults.Count)
|
||||
selectedIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7685e09f2a5b45dc82e8010310780446
|
||||
timeCreated: 1727611465
|
||||
@@ -0,0 +1,21 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Editor.Utility
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public static class TextureUtility
|
||||
{
|
||||
public static Texture2D MakeTexture(int width, int height, Color color)
|
||||
{
|
||||
var pixels = new Color[width * height];
|
||||
for (var i = 0; i < pixels.Length; i++)
|
||||
pixels[i] = color;
|
||||
var tex = new Texture2D(width, height);
|
||||
tex.SetPixels(pixels);
|
||||
tex.Apply();
|
||||
|
||||
return tex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73762c73e5e84089b53c84bbf6e66665
|
||||
timeCreated: 1716170448
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d63f7bdfd6b25a54e854ed23430eba56
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Events
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public class GameEventArgs : EventArgs
|
||||
{
|
||||
public new static GameEventArgs Empty => new GameEventArgs();
|
||||
|
||||
public object[] Args { get; }
|
||||
|
||||
public GameEventArgs(object eventArg)
|
||||
{
|
||||
Args = new[] { eventArg };
|
||||
}
|
||||
|
||||
public GameEventArgs(params object[] args)
|
||||
{
|
||||
Args = args;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 590da2e23275974478933650ab2f2157
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c52142ef5bc2ae4ea59e76830055910
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace BracerLib.Events.Interfaces
|
||||
{
|
||||
public interface IGameEvent
|
||||
{
|
||||
event Action<GameEventArgs> BeforeEvent;
|
||||
event Action<GameEventArgs> DuringEvent;
|
||||
event Action<GameEventArgs> AfterEvent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5495905009a14d24ad885fa5d12da919
|
||||
timeCreated: 1777815885
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace BracerLib.Events
|
||||
{
|
||||
[Serializable]
|
||||
public class UnityInteractionEvent : UnityEvent<Collider2D, Collider2D> { }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3153b3a444cdb9f48ab31c24120b4504
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c05a6a317b434df4ab536aed5fef6ba1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using BracerLib.Utility;
|
||||
|
||||
namespace BracerLib.StateManagement
|
||||
{
|
||||
/// <summary>
|
||||
/// Representation of a GameState enum that can be expanded without issue
|
||||
/// and into other classes of a base GameState.
|
||||
/// </summary>
|
||||
public class GameState : Enumeration
|
||||
{
|
||||
public GameState(int id, string name) : base(id, name) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c91277dea27634a4a849d3f5a0013533
|
||||
@@ -0,0 +1,349 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using BracerLib.StateManagement.Interfaces;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace BracerLib.StateManagement
|
||||
{
|
||||
public delegate IEnumerator GameStateEventHandler(GameState state);
|
||||
|
||||
[RequireComponent(typeof(StateMap))]
|
||||
[DefaultExecutionOrder(-999)]
|
||||
public class GameStateController : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Priority-specific holder of <see cref="GameStateEventHandler"/> events that need to trigger.
|
||||
/// </summary>
|
||||
private class GameStatePriorityEventHolder
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that houses all registered delegates to be invoked.
|
||||
/// </summary>
|
||||
public event GameStateEventHandler GameStateEventTrigger;
|
||||
/// <summary>
|
||||
/// Priority reference of this particular event holder.
|
||||
/// </summary>
|
||||
public GameStatePriority Priority { get; private set; }
|
||||
/// <summary>
|
||||
/// The list of delegates that can be invoked and yielded piece-meal rather than all at once via the assigned event.
|
||||
/// </summary>
|
||||
public IEnumerable<Delegate> InvocationList => GameStateEventTrigger?.GetInvocationList() ?? Array.Empty<Delegate>();
|
||||
|
||||
public GameStatePriorityEventHolder(GameStatePriority priority) => Priority = priority;
|
||||
|
||||
~GameStatePriorityEventHolder() => Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Get rid of all the attached delegates before clearing this out so no (less?) memory leaks
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
var invocations = InvocationList;
|
||||
foreach (var i in invocations)
|
||||
GameStateEventTrigger -= (GameStateEventHandler)i;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks priority events for a given state.
|
||||
/// </summary>
|
||||
private class GameStateEvent : IGameStateTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// Pre-allocation capacity for required states.
|
||||
/// </summary>
|
||||
private const int MAXSTATECAPACITY = 2;
|
||||
|
||||
public IDictionary<GameStatePriority, GameStatePriorityEventHolder> PrioritizedHandlers;
|
||||
|
||||
private bool isCompleted;
|
||||
private IList<IGameStateTrigger> requiredStates;
|
||||
private IGameStateTrigger[] cachedRequiredStates;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="GameState"/> this event represents.
|
||||
/// </summary>
|
||||
public GameState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GameState"/>s required before this state can be triggered.
|
||||
/// </summary>
|
||||
public IGameStateTrigger[] RequiredStates => cachedRequiredStates ??= requiredStates.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="GameState"/> is being processed. Can only be set true if all required
|
||||
/// triggers have been completed. If triggered prematurely, set into a queue state to be polled
|
||||
/// by <see cref="GameStateController"/> until it can be triggered.
|
||||
/// </summary>
|
||||
public bool IsTriggered { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Check to see if <see cref="GameState"/> has been completed. Will rely on Coroutines or async behavior.
|
||||
/// </summary>
|
||||
public bool IsComplete => IsTriggered && isCompleted;
|
||||
|
||||
public GameStateEvent(GameState state)
|
||||
{
|
||||
State = state;
|
||||
requiredStates = new List<IGameStateTrigger>(MAXSTATECAPACITY);
|
||||
|
||||
PrioritizedHandlers = new Dictionary<GameStatePriority, GameStatePriorityEventHolder>();
|
||||
|
||||
var priorities = Enum.GetValues(typeof(GameStatePriority));
|
||||
foreach (GameStatePriority p in priorities)
|
||||
PrioritizedHandlers.Add(p, new GameStatePriorityEventHolder(p));
|
||||
}
|
||||
|
||||
~GameStateEvent() => Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Add a state requirement for this event to follow.
|
||||
/// </summary>
|
||||
/// <param name="gameGameState">A state that must be complete before these events can be invoked.</param>
|
||||
public void AddRequiredState(IGameStateTrigger gameGameState)
|
||||
{
|
||||
if (!requiredStates.Contains(gameGameState))
|
||||
requiredStates.Add(gameGameState);
|
||||
}
|
||||
|
||||
public void Reset() => IsTriggered = false;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Reset();
|
||||
|
||||
requiredStates.Clear();
|
||||
|
||||
foreach (var pair in PrioritizedHandlers)
|
||||
pair.Value.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upon being triggered, go through and wait for each registered coroutine to run and complete.
|
||||
/// </summary>
|
||||
public IEnumerator WaitForGameStateEvents()
|
||||
{
|
||||
if (IsTriggered || isCompleted)
|
||||
yield break;
|
||||
|
||||
IsTriggered = true;
|
||||
|
||||
yield return null;
|
||||
|
||||
foreach (var p in PriorityStates)
|
||||
{
|
||||
var invocations = PrioritizedHandlers[p].InvocationList;
|
||||
foreach (var @delegate in invocations)
|
||||
{
|
||||
var handler = (GameStateEventHandler)@delegate;
|
||||
|
||||
// TODO: Figure out a way to get status or result from this
|
||||
yield return handler.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
isCompleted = true;
|
||||
}
|
||||
|
||||
private bool AllRequiredStatesTriggered()
|
||||
{
|
||||
var result = true;
|
||||
|
||||
foreach (var s in requiredStates)
|
||||
result &= s.IsTriggered;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool AllRequiredStatesCompleted()
|
||||
{
|
||||
var result = true;
|
||||
|
||||
foreach (var s in requiredStates)
|
||||
result &= s.IsComplete;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static GameStateController INSTANCE;
|
||||
private static IDictionary<GameState, GameStateEvent> STATE_EVENTS = new Dictionary<GameState, GameStateEvent>();
|
||||
|
||||
public static GameStateController Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (INSTANCE == null)
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
INSTANCE = FindAnyObjectByType<GameStateController>();
|
||||
#else
|
||||
INSTANCE = FindObjectOfType<GameStateController>();
|
||||
#endif
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
public static GameStatePriority[] PriorityStates { get; } = (GameStatePriority[])Enum.GetValues(typeof(GameStatePriority));
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to run the available action if a <see cref="GameState"/> has been triggered, meaning all its
|
||||
/// dependencies have been triggered as well.
|
||||
/// </summary>
|
||||
/// <param name="state">The target <see cref="GameState"/> state.</param>
|
||||
/// <param name="handler">The <see cref="GameStateEventHandler"/> handler or delegate to run.</param>
|
||||
/// <param name="priority">The <see cref="GameStatePriority"/> priority that the invocation should occur with.</param>
|
||||
/// <returns>A <see cref="GameStateInvocationStatus"/> status the user can act upon, if needed.</returns>
|
||||
public static GameStateInvocationStatus RunOrDefer(GameState state, GameStateEventHandler handler, GameStatePriority priority = GameStatePriority.Medium)
|
||||
{
|
||||
var triggered = IsGameStateTriggered(state) || IsGameStateComplete(state);
|
||||
if (triggered)
|
||||
{
|
||||
Instance.StartCoroutine(handler(state));
|
||||
|
||||
return GameStateInvocationStatus.Success;
|
||||
}
|
||||
|
||||
if (Instance == null && STATE_EVENTS.Count == 0)
|
||||
return GameStateInvocationStatus.Failure;
|
||||
|
||||
if (!STATE_EVENTS.TryGetValue(state, out var gameStateEvent))
|
||||
{
|
||||
Debug.LogError($"GameState invocation failure: Could not defer invocation of ${handler.Method.Name}.");
|
||||
|
||||
return GameStateInvocationStatus.Failure;
|
||||
}
|
||||
|
||||
STATE_EVENTS[state].PrioritizedHandlers[priority].GameStateEventTrigger += handler;
|
||||
|
||||
return GameStateInvocationStatus.Deferred;
|
||||
}
|
||||
|
||||
public static bool IsGameStateTriggered(GameState state) => STATE_EVENTS.TryGetValue(state, out var stateEvent) && stateEvent.IsTriggered;
|
||||
|
||||
public static bool IsGameStateComplete(GameState state) => STATE_EVENTS.TryGetValue(state, out var stateEvent) && stateEvent.IsComplete;
|
||||
|
||||
private static void ResetStateFlags()
|
||||
{
|
||||
foreach (var entry in STATE_EVENTS)
|
||||
entry.Value.Reset();
|
||||
}
|
||||
|
||||
private StateMap stateMap;
|
||||
private bool isTriggered;
|
||||
private bool isCompleted;
|
||||
private Coroutine initCoroutine;
|
||||
private YieldInstruction frameWait;
|
||||
private Stopwatch stateStopwatch;
|
||||
private Stopwatch overallStopwatch;
|
||||
|
||||
/// <summary>
|
||||
/// Bootstrap all state dependencies upon awake so they can be registered first
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
INSTANCE = this;
|
||||
stateStopwatch = new Stopwatch();
|
||||
overallStopwatch = new Stopwatch();
|
||||
|
||||
if (STATE_EVENTS.Count > 0)
|
||||
{
|
||||
Debug.LogError("No GameStates recognized. Exiting state registration. You've more than likely done something very wrong.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
frameWait = new WaitForEndOfFrame();
|
||||
|
||||
stateMap = GetComponent<StateMap>();
|
||||
|
||||
foreach (var v in stateMap.GetGameStateMap())
|
||||
STATE_EVENTS.Add(v, new GameStateEvent(v));
|
||||
|
||||
// Add state dependency map. Left-hand GameState requires right-hand GameState to be finished before proceeding
|
||||
// Supports multiple-required states. Read as "LeftHandState requires RightHandState."
|
||||
// New states can be added in this chain, but they must be linked from one end to the other
|
||||
// e.g. (GameState.B, GameState.A), -> GameState.B requires GameState.A
|
||||
// (GameState.C, GameState.B) -> GameState.C requires GameState.B
|
||||
foreach (var t in stateMap.GetGameStateRequirements())
|
||||
STATE_EVENTS[t.Item1].AddRequiredState(STATE_EVENTS[t.Item2]);
|
||||
}
|
||||
|
||||
private void Start() => TriggerInitialization();
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
INSTANCE = null;
|
||||
|
||||
STATE_EVENTS.Clear();
|
||||
|
||||
foreach (var entry in STATE_EVENTS)
|
||||
entry.Value.Clear();
|
||||
}
|
||||
|
||||
private void TriggerInitialization()
|
||||
{
|
||||
if (initCoroutine != null)
|
||||
return;
|
||||
|
||||
initCoroutine = StartCoroutine(PerformInitialization());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run events attached to all states running up the chain of requirements for <see cref="GameStateEvent"/>.
|
||||
/// </summary>
|
||||
private IEnumerator PerformInitialization()
|
||||
{
|
||||
ResetStateFlags();
|
||||
|
||||
// Allow all other objects in scene to wake up
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
var readyState = stateMap.GetReadyState();
|
||||
|
||||
IGameStateTriggerInvoker targetEvent;
|
||||
while ((targetEvent = NextAvailableGameStateEvent(readyState)) != null)
|
||||
{
|
||||
Debug.Log($"Running GameState: {targetEvent.State}");
|
||||
stateStopwatch.Restart();
|
||||
|
||||
yield return StartCoroutine(targetEvent.WaitForGameStateEvents());
|
||||
stateStopwatch.Stop();
|
||||
|
||||
yield return frameWait;
|
||||
|
||||
var span = TimeSpan.FromMilliseconds(stateStopwatch.ElapsedMilliseconds);
|
||||
Debug.Log($"{targetEvent.State}: {span.Minutes:D2} minutes, {span.Seconds:D2} seconds, {span.Milliseconds:D3} ms");
|
||||
}
|
||||
|
||||
var overallSpan = TimeSpan.FromMilliseconds(stateStopwatch.ElapsedMilliseconds);
|
||||
Debug.Log($"Finish initializing all GameStates\n{overallSpan.Minutes:D2} minutes, {overallSpan.Seconds:D2} seconds, {overallSpan.Milliseconds:D3} ms");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively traverse the required <see cref="GameStateEvent"/> that each event may have until it reaches
|
||||
/// the state that needs to be run next.
|
||||
/// </summary>
|
||||
/// <param name="state"><see cref="GameState"/> State to start traversal from.</param>
|
||||
/// <returns>The earliest <see cref="GameStateEvent"/> event whose events have not yet triggered.</returns>
|
||||
private IGameStateTrigger NextAvailableGameStateEvent(GameState state)
|
||||
{
|
||||
var currentEvent = STATE_EVENTS[state];
|
||||
var states = currentEvent.RequiredStates;
|
||||
var index = 0;
|
||||
while (index < states.Length)
|
||||
{
|
||||
var target = NextAvailableGameStateEvent(states[index++].State);
|
||||
|
||||
if (target != null && !target.IsTriggered)
|
||||
return target;
|
||||
}
|
||||
|
||||
return !currentEvent.IsTriggered ? currentEvent : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e7c95ba8e0d6d24b8d1492525d538ae
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BracerLib.StateManagement
|
||||
{
|
||||
public enum GameStateInvocationStatus
|
||||
{
|
||||
Failure,
|
||||
Deferred,
|
||||
Success
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21f5fc392c0ba374b9eb8f0950b6a799
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace BracerLib.StateManagement
|
||||
{
|
||||
public enum GameStatePriority
|
||||
{
|
||||
Highest,
|
||||
High,
|
||||
Medium,
|
||||
Low,
|
||||
Lowest
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 140bdf34ce09b7b47b9a681fb52bf8b6
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79e978ffe1b3a3249a3a451d36aeb3c0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace BracerLib.StateManagement.Interfaces
|
||||
{
|
||||
public interface IGameStateIdentifier
|
||||
{
|
||||
GameState State { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b426d591d7141ce9762bc4a762a865d
|
||||
timeCreated: 1697030279
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace BracerLib.StateManagement.Interfaces
|
||||
{
|
||||
public interface IGameStateTrigger : IGameStateTriggerStatus, IGameStateTriggerInvoker { }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8400e946acf14bfd89ee59f69c4a44ae
|
||||
timeCreated: 1697030243
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace BracerLib.StateManagement.Interfaces
|
||||
{
|
||||
public interface IGameStateTriggerInvoker : IGameStateIdentifier
|
||||
{
|
||||
IEnumerator WaitForGameStateEvents();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90d8238af53645aca1a06d611b4c15c4
|
||||
timeCreated: 1697030264
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BracerLib.StateManagement.Interfaces
|
||||
{
|
||||
public interface IGameStateTriggerStatus
|
||||
{
|
||||
bool IsTriggered { get; }
|
||||
bool IsComplete { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42365a62862043f693a71a439c00a693
|
||||
timeCreated: 1697030254
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BracerLib.StateManagement
|
||||
{
|
||||
/// <summary>
|
||||
/// Base StateMap component that can be extended and used for start up or other state loads.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-1000)]
|
||||
public class StateMap : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Method to send back the number of states to be utilized.
|
||||
/// </summary>
|
||||
public virtual GameState[] GetGameStateMap() => Array.Empty<GameState>();
|
||||
|
||||
/// <summary>
|
||||
/// The running requirement set for all <see cref="GameState"/> state entries.
|
||||
/// </summary>
|
||||
public virtual Tuple<GameState, GameState>[] GetGameStateRequirements() => Array.Empty<Tuple<GameState, GameState>>();
|
||||
|
||||
/// <summary>
|
||||
/// What is considered the last game ready state to run before starting the game.
|
||||
/// </summary>
|
||||
public virtual GameState GetReadyState() => null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3f8dd6850d6fa144b4aabaceaadfeac
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20492f3cdddb8734884b33efce400a39
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "BracerLib.Tests",
|
||||
"rootNamespace": "BracerLib.Tests",
|
||||
"references": [
|
||||
"UnityEngine.TestRunner",
|
||||
"BracerLib"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll",
|
||||
"Moq.dll"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f994df8a4a2196f499e769b079127c12
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Tests
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[ExcludeFromCoverage]
|
||||
public class MonoBehaviourTester : MonoBehaviour, IMonoBehaviourTest
|
||||
{
|
||||
public virtual bool IsTestFinished { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e2baafc97e51834e89c53aaa65ddd67
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ca20ad462434c06ad77f83ad433b8e3
|
||||
timeCreated: 1778953578
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using Assert = UnityEngine.Assertions.Assert;
|
||||
|
||||
namespace BracerLib.Tests.Objects
|
||||
{
|
||||
public class ObjectLifetimeTests : TestBase
|
||||
{
|
||||
private class SomeDisposable : IDisposable
|
||||
{
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
IsDisposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
private SomeDisposable disposableObject;
|
||||
private SomeDisposable disposableFuncObject;
|
||||
private GameObject singleTestLifetimeObject;
|
||||
private GameObject singleTestLifetimeFuncObject;
|
||||
private GameObject completeTestLifetimeObject;
|
||||
private GameObject completeTestLifetimeFuncObject;
|
||||
private bool singleTestWasCreated;
|
||||
|
||||
[UnityTest, Order(1)]
|
||||
public IEnumerator SetUpOneTimeObjects()
|
||||
{
|
||||
completeTestLifetimeObject = new GameObject("full lifetime");
|
||||
RegisterOneTimeTestObject(completeTestLifetimeObject);
|
||||
|
||||
yield return null;
|
||||
Assert.IsTrue(completeTestLifetimeObject != null);
|
||||
|
||||
completeTestLifetimeFuncObject = RegisterOneTimeTestObject(GetNewObject);
|
||||
|
||||
yield return null;
|
||||
Assert.IsTrue(completeTestLifetimeFuncObject != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not here to test Unity code, but want to run through a basic shift of the object position.
|
||||
/// </summary>
|
||||
[UnityTest, Order(2)]
|
||||
public IEnumerator TempUnityObjectMovesCorrectly()
|
||||
{
|
||||
Assert.IsFalse(singleTestWasCreated);
|
||||
|
||||
singleTestLifetimeObject = new GameObject("singleTestLifetime");
|
||||
RegisterTempTestObject(singleTestLifetimeObject);
|
||||
singleTestLifetimeFuncObject = RegisterTempTestObject(GetNewObject);
|
||||
singleTestWasCreated = true;
|
||||
|
||||
Assert.IsTrue(singleTestWasCreated);
|
||||
|
||||
yield return null;
|
||||
|
||||
var testTransform = singleTestLifetimeObject.transform;
|
||||
var tPos = testTransform.position;
|
||||
var lastPos = tPos;
|
||||
tPos = Vector3.one;
|
||||
testTransform.position = tPos;
|
||||
|
||||
Assert.IsTrue(tPos != lastPos);
|
||||
|
||||
testTransform.Translate(Vector3.up);
|
||||
|
||||
yield return null;
|
||||
|
||||
tPos = testTransform.position;
|
||||
|
||||
Assert.IsTrue(tPos != lastPos);
|
||||
}
|
||||
|
||||
[UnityTest, Order(3)]
|
||||
public IEnumerator TempUnityObjectNoLongerExistsAfterPreviousTest()
|
||||
{
|
||||
Assert.IsTrue(singleTestWasCreated);
|
||||
Assert.IsNull(singleTestLifetimeObject);
|
||||
Assert.IsNull(singleTestLifetimeFuncObject);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTest, Order(4)]
|
||||
public IEnumerator OneTimeObjectStillExists()
|
||||
{
|
||||
Assert.IsTrue(completeTestLifetimeObject != null);
|
||||
Assert.IsTrue(completeTestLifetimeFuncObject != null);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[Test, Order(5)]
|
||||
public void RegisterDisposableObject()
|
||||
{
|
||||
disposableObject = new SomeDisposable();
|
||||
RegisterDisposableTempTestObject(disposableObject);
|
||||
Assert.IsNotNull(disposableObject);
|
||||
Assert.IsFalse(disposableObject.IsDisposed);
|
||||
|
||||
disposableFuncObject = RegisterDisposableTempTestObject(() => new SomeDisposable());
|
||||
Assert.IsNotNull(disposableFuncObject);
|
||||
Assert.IsFalse(disposableFuncObject.IsDisposed);
|
||||
}
|
||||
|
||||
[Test, Order(6)]
|
||||
public void CheckIfDisposableObjectHasDisposed()
|
||||
{
|
||||
Assert.IsNotNull(disposableObject);
|
||||
Assert.IsTrue(disposableObject.IsDisposed);
|
||||
|
||||
disposableObject = null;
|
||||
|
||||
Assert.IsNotNull(disposableFuncObject);
|
||||
Assert.IsTrue(disposableFuncObject.IsDisposed);
|
||||
|
||||
disposableObject = null;
|
||||
}
|
||||
|
||||
private GameObject GetNewObject() => new GameObject();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78520c491186453a91097eac4579ed75
|
||||
timeCreated: 1778931474
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BracerLib.Tests.Objects
|
||||
{
|
||||
public interface ITester
|
||||
{
|
||||
void Test();
|
||||
}
|
||||
|
||||
public class Tester : ITester
|
||||
{
|
||||
public void Test() {}
|
||||
}
|
||||
|
||||
public class MockComponent : MonoBehaviour
|
||||
{
|
||||
private ITester tester;
|
||||
|
||||
public void DoTest()
|
||||
{
|
||||
tester.Test();
|
||||
}
|
||||
}
|
||||
|
||||
public class ObjectMockTests : TestBase
|
||||
{
|
||||
private MockComponent mockComponent;
|
||||
private Mock<ITester> testerMock;
|
||||
|
||||
protected override IEnumerator UnityOneTimeSetUp()
|
||||
{
|
||||
testerMock = new Mock<ITester>();
|
||||
|
||||
var go = new GameObject("mock test obj");
|
||||
mockComponent = go.AddComponent<MockComponent>();
|
||||
RegisterOneTimeTestObject(go);
|
||||
SetReflectedValue(mockComponent, "tester", testerMock.Object);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMock()
|
||||
{
|
||||
testerMock.Setup(x => x.Test());
|
||||
|
||||
mockComponent.DoTest();
|
||||
|
||||
testerMock.Verify(x => x.Test(), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45e688432fb9423e8efb7c88cdbd9785
|
||||
timeCreated: 1779762115
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82e81a731adf4d6fbab5315db59006d2
|
||||
timeCreated: 1778983978
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Tests.Properties
|
||||
{
|
||||
public class PropertyTests : TestBase
|
||||
{
|
||||
public class SomeObject : MonoBehaviour
|
||||
{
|
||||
public DependencyA dependencyA;
|
||||
|
||||
[SerializeField]
|
||||
private DependencyB dependencyB1;
|
||||
[SerializeField]
|
||||
private DependencyB dependencyB2;
|
||||
[SerializeField]
|
||||
private DependencyB dependencyB3;
|
||||
|
||||
private DependencyC dependencyC;
|
||||
private DependencyD dependencyD;
|
||||
private ValueDependency valueDependency;
|
||||
private int someValue;
|
||||
|
||||
public DependencyB DependencyB1 => dependencyB1;
|
||||
public DependencyB DependencyB2 => dependencyB2;
|
||||
public DependencyB DependencyB3 => dependencyB3;
|
||||
public DependencyC DependencyC => dependencyC;
|
||||
public DependencyD DependencyD => dependencyD;
|
||||
public ValueDependency ValueDependency => valueDependency;
|
||||
public int SomeValue => someValue;
|
||||
}
|
||||
|
||||
public class DependencyA : MonoBehaviour
|
||||
{
|
||||
public int Value => 1;
|
||||
}
|
||||
|
||||
public class DependencyB : MonoBehaviour
|
||||
{
|
||||
public int Value => 2;
|
||||
}
|
||||
|
||||
public class DependencyC : MonoBehaviour
|
||||
{
|
||||
public int Value => 3;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DependencyD { }
|
||||
|
||||
public struct ValueDependency
|
||||
{
|
||||
public int Value;
|
||||
}
|
||||
|
||||
private GameObject setupGameObject;
|
||||
private SomeObject setupSomeObject;
|
||||
private DependencyA setupDependencyA;
|
||||
|
||||
protected override IEnumerator UnityOneTimeSetUp()
|
||||
{
|
||||
setupGameObject = new GameObject("setup");
|
||||
setupSomeObject = setupGameObject.AddComponent<SomeObject>();
|
||||
setupDependencyA = setupGameObject.AddComponent<DependencyA>();
|
||||
|
||||
setupSomeObject.dependencyA = setupDependencyA;
|
||||
RegisterOneTimeTestObject(setupSomeObject);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TestProperties()
|
||||
{
|
||||
Assert.IsNotNull(setupSomeObject);
|
||||
Assert.IsNotNull(setupDependencyA);
|
||||
|
||||
// Set Serialized private Mono
|
||||
var depB = new[] { setupGameObject.AddComponent<DependencyB>(), setupGameObject.AddComponent<DependencyB>(), setupGameObject.AddComponent<DependencyB>() };
|
||||
SetObjectReference(setupSomeObject, "dependencyB1", depB[0]);
|
||||
SetObjectReferences(setupSomeObject, ("dependencyB2", depB[1]), ("dependencyB3", depB[2]));
|
||||
|
||||
// Set Reflected private Mono
|
||||
var depC = setupGameObject.AddComponent<DependencyC>();
|
||||
SetReflectedValue(setupSomeObject, "dependencyC", depC);
|
||||
|
||||
// Set Reflected private POCO
|
||||
// Set Reflected private value
|
||||
var depD = new DependencyD();
|
||||
var valueDependency = new ValueDependency { Value = 255 };
|
||||
SetReflectedValues(setupSomeObject, ("dependencyD", depD), ("valueDependency", valueDependency));
|
||||
|
||||
yield return null;
|
||||
|
||||
Assert.IsNotNull(depB[0]);
|
||||
Assert.IsNotNull(setupSomeObject.DependencyB1);
|
||||
Assert.AreEqual(depB[0], setupSomeObject.DependencyB1);
|
||||
|
||||
Assert.IsNotNull(depB[1]);
|
||||
Assert.IsNotNull(setupSomeObject.DependencyB2);
|
||||
Assert.AreEqual(depB[1], setupSomeObject.DependencyB2);
|
||||
|
||||
Assert.IsNotNull(depB[2]);
|
||||
Assert.IsNotNull(setupSomeObject.DependencyB3);
|
||||
Assert.AreEqual(depB[2], setupSomeObject.DependencyB3);
|
||||
|
||||
Assert.IsNotNull(depC);
|
||||
Assert.IsNotNull(setupSomeObject.DependencyC);
|
||||
Assert.AreEqual(depC, setupSomeObject.DependencyC);
|
||||
|
||||
Assert.IsNotNull(depD);
|
||||
Assert.IsNotNull(setupSomeObject.DependencyD);
|
||||
Assert.AreEqual(depD, setupSomeObject.DependencyD);
|
||||
|
||||
Assert.AreEqual(setupSomeObject.ValueDependency.Value, valueDependency.Value);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TestPropertyErrors()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
SetReflectedValue(setupSomeObject, "someFakeValue", 1);
|
||||
LogAssert.Expect(LogType.Warning, "There is no property someFakeValue to the target object.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce9835c5996a47e6a286fabb5d421d3e
|
||||
timeCreated: 1778983994
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f662576f6c6c422d810fcd5290ec107c
|
||||
timeCreated: 1778977478
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Tests.Scenes
|
||||
{
|
||||
public class SceneTests : TestBase
|
||||
{
|
||||
private Scene oneTimeScene;
|
||||
private Scene testScene;
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator CloseSceneWithZeroScenesRegistered()
|
||||
{
|
||||
var sceneCount = SceneManager.loadedSceneCount;
|
||||
Assert.IsTrue(sceneCount > 0);
|
||||
|
||||
yield return CloseLatestScene();
|
||||
|
||||
Assert.IsTrue(SceneManager.loadedSceneCount == sceneCount);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator CreateScenesWithSpecificLifetimes()
|
||||
{
|
||||
yield return OpenScene("Scenes/Tests/Test_Empty", true);
|
||||
oneTimeScene = SceneManager.GetSceneAt(SceneManager.loadedSceneCount - 1);
|
||||
Assert.IsTrue(oneTimeScene.isLoaded);
|
||||
|
||||
yield return OpenScene("Scenes/Tests/Test_Empty");
|
||||
testScene = SceneManager.GetSceneAt(SceneManager.loadedSceneCount - 1);
|
||||
Assert.IsTrue(testScene.isLoaded);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TestSceneLifetimes()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
Assert.IsTrue(oneTimeScene.isLoaded);
|
||||
Assert.IsFalse(testScene.isLoaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c5d9b3d87fe4f1dab685f84ed847fdf
|
||||
timeCreated: 1778977483
|
||||
@@ -0,0 +1,413 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace BracerLib.Tests
|
||||
{
|
||||
[ExcludeFromCodeCoverage, ExcludeFromCoverage]
|
||||
public class TestBase
|
||||
{
|
||||
// private readonly IDictionary<Object, SerializedObject> globalPropertyCache;
|
||||
private readonly IDictionary<Object, SerializedObject> objectPropertyCache;
|
||||
private readonly Queue<Object> destroyOnTestEnd;
|
||||
private readonly Stack<Scene> closeOnTestEnd;
|
||||
private readonly Queue<IDisposable> disposeOnTestEnd;
|
||||
private readonly Queue<Object> destroyOnOneTimeEnd;
|
||||
private readonly Stack<Scene> closeOnOneTimeEnd;
|
||||
private readonly Queue<IDisposable> disposeOnOneTimeEnd;
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
protected TestBase()
|
||||
{
|
||||
// globalPropertyCache = new Dictionary<Object, SerializedObject>();
|
||||
objectPropertyCache = new Dictionary<Object, SerializedObject>();
|
||||
destroyOnTestEnd = new Queue<Object>();
|
||||
closeOnTestEnd = new Stack<Scene>();
|
||||
disposeOnTestEnd = new Queue<IDisposable>();
|
||||
destroyOnOneTimeEnd = new Queue<Object>();
|
||||
closeOnOneTimeEnd = new Stack<Scene>();
|
||||
disposeOnOneTimeEnd = new Queue<IDisposable>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called as part of NUnit framework. Override <see cref="OneTimeSetUp"/> instead.
|
||||
/// </summary>
|
||||
[OneTimeSetUp, ExcludeFromCoverage]
|
||||
public void DoOneTimeSetUp()
|
||||
{
|
||||
OneTimeSetUp();
|
||||
}
|
||||
|
||||
[UnityOneTimeSetUp, ExcludeFromCoverage]
|
||||
public IEnumerator DoUnityOneTimeSetUp()
|
||||
{
|
||||
yield return UnityOneTimeSetUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called as part of NUnit framework. Override <see cref="SetUp"/> instead.
|
||||
/// </summary>
|
||||
[SetUp, ExcludeFromCoverage]
|
||||
public void DoSetUp()
|
||||
{
|
||||
SetUp();
|
||||
}
|
||||
|
||||
[UnitySetUp, ExcludeFromCoverage]
|
||||
public IEnumerator DoUnitySetUp()
|
||||
{
|
||||
yield return UnitySetUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called as part of NUnit framework. Override <see cref="TearDown"/> instead.
|
||||
/// </summary>
|
||||
[TearDown, ExcludeFromCoverage]
|
||||
public void DoTearDown()
|
||||
{
|
||||
TearDown();
|
||||
|
||||
objectPropertyCache.Clear();
|
||||
}
|
||||
|
||||
[UnityTearDown, ExcludeFromCoverage]
|
||||
public IEnumerator DoUnityTearDown()
|
||||
{
|
||||
yield return UnityTearDown();
|
||||
|
||||
Reset();
|
||||
|
||||
while (closeOnTestEnd.Count > 0)
|
||||
yield return CloseLatestScene();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called as part of NUnit framework. Override <see cref="OneTimeTearDown"/> instead.
|
||||
/// </summary>
|
||||
[OneTimeTearDown, ExcludeFromCoverage]
|
||||
public void DoOneTimeTearDown()
|
||||
{
|
||||
OneTimeTearDown();
|
||||
}
|
||||
|
||||
[UnityOneTimeTearDown, ExcludeFromCoverage]
|
||||
public IEnumerator DoUnityOneTimeTearDown()
|
||||
{
|
||||
yield return UnityOneTimeTearDown();
|
||||
|
||||
CleanupUnityObjects(destroyOnOneTimeEnd);
|
||||
CleanupDisposableObjects(disposeOnOneTimeEnd);
|
||||
|
||||
while (closeOnOneTimeEnd.Count > 0)
|
||||
yield return CloseLatestScene(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass a Unity object that will be destroyed at the end of the test.
|
||||
/// </summary>
|
||||
protected T RegisterTempTestObject<T>(T obj) where T : Object
|
||||
{
|
||||
destroyOnTestEnd.Enqueue(obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass a function that generates or returns a Unity object that will be destroyed at the end of the test.
|
||||
/// </summary>
|
||||
protected T RegisterTempTestObject<T>(Func<T> generator) where T : Object
|
||||
{
|
||||
var obj = generator();
|
||||
|
||||
return RegisterTempTestObject(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass a Unity object that will be destroyed at the end of all the tests in a given suite.
|
||||
/// </summary>
|
||||
protected T RegisterOneTimeTestObject<T>(T obj) where T : Object
|
||||
{
|
||||
destroyOnOneTimeEnd.Enqueue(obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass a function that generates or returns a Unity object that will be destroyed at the end of all the tests in a given suite.
|
||||
/// </summary>
|
||||
protected T RegisterOneTimeTestObject<T>(Func<T> generator) where T : Object
|
||||
{
|
||||
var obj = generator();
|
||||
|
||||
return RegisterOneTimeTestObject(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass a disposable object that will be cleaned up at the end of the test.
|
||||
/// </summary>
|
||||
protected T RegisterDisposableTempTestObject<T>(T obj) where T : IDisposable
|
||||
{
|
||||
disposeOnTestEnd.Enqueue(obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass a function that generates or returns a disposable object that will be cleaned up at the end of the test.
|
||||
/// </summary>
|
||||
/// <param name="generator"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
protected T RegisterDisposableTempTestObject<T>(Func<T> generator) where T : IDisposable
|
||||
{
|
||||
var obj = generator();
|
||||
|
||||
return RegisterDisposableTempTestObject(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass a disposable object that will be cleaned up at the end of the test.
|
||||
/// </summary>
|
||||
protected T RegisterDisposableOneTimeTestObject<T>(T obj) where T : IDisposable
|
||||
{
|
||||
disposeOnOneTimeEnd.Enqueue(obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass a function that generates or returns a disposable object that will be cleaned up at the end of the test.
|
||||
/// </summary>
|
||||
/// <param name="generator"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
protected T RegisterDisposableOneTimeTestObject<T>(Func<T> generator) where T : IDisposable
|
||||
{
|
||||
var obj = generator();
|
||||
|
||||
return RegisterDisposableOneTimeTestObject(obj);
|
||||
}
|
||||
|
||||
protected IEnumerator OpenScene(string scenePath, bool isOneTime = false)
|
||||
{
|
||||
var asyncOp = SceneManager.LoadSceneAsync(scenePath, LoadSceneMode.Additive);
|
||||
asyncOp!.completed += SetLoadedSceneActive;
|
||||
|
||||
yield return asyncOp;
|
||||
|
||||
var loadedScene = SceneManager.GetSceneAt(SceneManager.loadedSceneCount - 1);
|
||||
if (!isOneTime)
|
||||
closeOnTestEnd.Push(loadedScene);
|
||||
else
|
||||
closeOnOneTimeEnd.Push(loadedScene);
|
||||
}
|
||||
|
||||
protected IEnumerator CloseLatestScene(bool isOneTime = false)
|
||||
{
|
||||
var targetStack = !isOneTime ? closeOnTestEnd : closeOnOneTimeEnd;
|
||||
|
||||
if (!targetStack.TryPop(out var targetScene))
|
||||
yield break;
|
||||
|
||||
var asyncOp = SceneManager.UnloadSceneAsync(targetScene);
|
||||
asyncOp!.completed += SetLoadedSceneActive;
|
||||
|
||||
yield return asyncOp;
|
||||
}
|
||||
|
||||
protected void SetLoadedSceneActive(AsyncOperation asyncOperation)
|
||||
{
|
||||
SceneManager.SetActiveScene(SceneManager.GetSceneAt(SceneManager.loadedSceneCount - 1));
|
||||
}
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
protected virtual void OneTimeSetUp() { }
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
protected virtual IEnumerator UnityOneTimeSetUp()
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
protected virtual void SetUp() { }
|
||||
|
||||
protected virtual IEnumerator UnitySetUp()
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
protected virtual void TearDown() { }
|
||||
|
||||
protected virtual IEnumerator UnityTearDown()
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
protected virtual void OneTimeTearDown() { }
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
protected virtual IEnumerator UnityOneTimeTearDown()
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a private value member of an object via C# reflection.
|
||||
/// </summary>
|
||||
/// <example><code>
|
||||
/// public class SomeObject {
|
||||
/// private int item;
|
||||
/// }
|
||||
///
|
||||
/// var obj = new SomeObject();
|
||||
/// SetReflectedValue(obj, "item", 25);
|
||||
/// </code></example>
|
||||
private protected void SetReflectedValue(object targetObject, string targetProperty, object targetValue)
|
||||
{
|
||||
SetReflectedValue(targetObject, targetObject.GetType(), targetProperty, targetValue);
|
||||
}
|
||||
|
||||
private protected void SetReflectedValues(object targetObject, params ValueTuple<string, object>[] properties)
|
||||
{
|
||||
var type = targetObject.GetType();
|
||||
|
||||
for (var i = 0; i < properties.Length; i++)
|
||||
SetReflectedValue(targetObject, type, properties[i].Item1, properties[i].Item2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a non-interface reference member of a MonoBehaviour via Unity reflection.
|
||||
/// Will be applied depending on context of Test setup being used.
|
||||
/// </summary>
|
||||
/// <example><code>
|
||||
/// public class SomeOtherObject { }
|
||||
///
|
||||
/// public class SomeObject : MonoBehaviour {
|
||||
/// private SomeOtherObject item;
|
||||
/// }
|
||||
///
|
||||
/// var gameObj = new GameObject("SomeObj");
|
||||
/// var c = gameObj.AddComponent(typeof(SomeObject));
|
||||
/// var someOtherObj = new SomeOtherObj();
|
||||
///
|
||||
/// SetObjectReference(c, "item", someOtherObj);
|
||||
/// </code></example>
|
||||
private protected void SetObjectReference(Object targetObj, string targetValue, Object value)
|
||||
{
|
||||
if (!objectPropertyCache.TryGetValue(targetObj, out var serializedObject))
|
||||
{
|
||||
serializedObject = new SerializedObject(targetObj);
|
||||
objectPropertyCache.Add(targetObj, serializedObject);
|
||||
}
|
||||
|
||||
var property = serializedObject.FindProperty(targetValue);
|
||||
property.objectReferenceValue = value;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a non-interface reference member of a MonoBehaviour via Unity reflection.
|
||||
/// Will be applied depending on context of Test setup being used.
|
||||
/// </summary>
|
||||
/// <example><code>
|
||||
/// public class SomeObject : MonoBehaviour {
|
||||
/// private SomeOtherObject item1;
|
||||
/// private SomeAdditionalObject item2;
|
||||
/// }
|
||||
///
|
||||
/// var gameObj = new GameObject("SomeObj");
|
||||
/// var c = gameObj.AddComponent(typeof(SomeObject));
|
||||
///
|
||||
/// SetObjectReference(c, a);
|
||||
/// </code></example>
|
||||
private protected void SetObjectReferences(Object targetObj, params ValueTuple<string, Object>[] properties)
|
||||
{
|
||||
for (var i = 0; i < properties.Length; i++)
|
||||
SetObjectReference(targetObj, properties[i].Item1, properties[i].Item2);
|
||||
}
|
||||
|
||||
private void SetReflectedValue(object targetObject, Type type, string targetProperty, object targetValue)
|
||||
{
|
||||
var fieldProperty = type.GetField(targetProperty, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (fieldProperty == null)
|
||||
{
|
||||
Debug.LogWarning($"There is no property {targetProperty} to the target object.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
fieldProperty.SetValue(targetObject, targetValue);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
ResetAllMocks();
|
||||
CleanupDisposableObjects(disposeOnTestEnd);
|
||||
CleanupUnityObjects(destroyOnTestEnd);
|
||||
}
|
||||
|
||||
private void CleanupUnityObjects(Queue<Object> unityObjects)
|
||||
{
|
||||
if (unityObjects.Count == 0)
|
||||
return;
|
||||
|
||||
while (unityObjects.Count != 0)
|
||||
{
|
||||
var temp = unityObjects.Dequeue();
|
||||
if (temp != null)
|
||||
Object.DestroyImmediate(temp);
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupDisposableObjects(Queue<IDisposable> disposableObjects)
|
||||
{
|
||||
if (disposableObjects.Count == 0)
|
||||
return;
|
||||
|
||||
while (disposableObjects.Count != 0)
|
||||
{
|
||||
var temp = disposableObjects.Dequeue();
|
||||
temp?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
private void ResetAllMocks()
|
||||
{
|
||||
var mocks = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(f => f.FieldType == typeof(Mock)).Select(f => (Mock)f.GetValue(this));
|
||||
|
||||
foreach (var m in mocks)
|
||||
m.Reset();
|
||||
}
|
||||
|
||||
// [ExcludeFromCoverage]
|
||||
// private void Apply(bool isGlobal = false)
|
||||
// {
|
||||
// ApplyPropertyCache(objectPropertyCache);
|
||||
// }
|
||||
//
|
||||
// [ExcludeFromCoverage]
|
||||
// private void ApplyPropertyCache(IDictionary<Object, SerializedObject> cache)
|
||||
// {
|
||||
// if (cache.Count == 0)
|
||||
// return;
|
||||
//
|
||||
// foreach (var pair in cache)
|
||||
// pair.Value.ApplyModifiedProperties();
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1863291fabd7fb43837115239feede3
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3b38a9a1e7f41b8849eb925b8dda6ef
|
||||
timeCreated: 1686834699
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections;
|
||||
using BracerLib.Utility;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BracerLib.Tests.Utility
|
||||
{
|
||||
public class MathUtilityTests : TestBase
|
||||
{
|
||||
private static IEnumerable BoundValues()
|
||||
{
|
||||
yield return new TestCaseData(2, 1, 3, 2).SetName("Value is between bounds");
|
||||
yield return new TestCaseData(0, 1, 3, 3).SetName("Value is below bounds");
|
||||
yield return new TestCaseData(4, 1, 3, 1).SetName("Value is above bounds");
|
||||
}
|
||||
|
||||
private static IEnumerable BetweenValuesInt()
|
||||
{
|
||||
yield return new TestCaseData(0, -1, 1, BoundsInclusivity.None, true).SetName("Value is between bounds");
|
||||
yield return new TestCaseData(-2, -1, 1, BoundsInclusivity.None, false).SetName("Value is below bounds");
|
||||
yield return new TestCaseData(2, -1, 1, BoundsInclusivity.None, false).SetName("Value is above bounds");
|
||||
yield return new TestCaseData(-1, -1, 1, BoundsInclusivity.Both, true).SetName("Value is at bound, inclusive both");
|
||||
yield return new TestCaseData(-1, -1, 1, BoundsInclusivity.Left, true).SetName("Value is at bound, inclusive left");
|
||||
yield return new TestCaseData(1, -1, 1, BoundsInclusivity.Right, true).SetName("Value is at bound, inclusive right");
|
||||
yield return new TestCaseData(1, -1, 1, BoundsInclusivity.Both, true).SetName("Value is at bound, exclusive");
|
||||
}
|
||||
|
||||
private static IEnumerable BetweenValuesFloat()
|
||||
{
|
||||
yield return new TestCaseData(0f, -1f, 1f, BoundsInclusivity.None, true).SetName("Value is between bounds");
|
||||
yield return new TestCaseData(-2f, -1f, 1f, BoundsInclusivity.None, false).SetName("Value is below bounds");
|
||||
yield return new TestCaseData(2f, -1f, 1f, BoundsInclusivity.None, false).SetName("Value is above bounds");
|
||||
yield return new TestCaseData(-1f, -1f, 1f, BoundsInclusivity.Both, true).SetName("Value is at bound, inclusive both");
|
||||
yield return new TestCaseData(-1f, -1f, 1f, BoundsInclusivity.Left, true).SetName("Value is at bound, inclusive left");
|
||||
yield return new TestCaseData(1f, -1f, 1f, BoundsInclusivity.Right, true).SetName("Value is at bound, inclusive right");
|
||||
yield return new TestCaseData(1f, -1f, 1f, BoundsInclusivity.Both, true).SetName("Value is at bound, exclusive");
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(BoundValues))]
|
||||
public void ValuesAreWithinBounds(int value, int min, int max, int expected)
|
||||
{
|
||||
var result = MathUtility.KeepWithinBounds(value, min, max);
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(BetweenValuesInt))]
|
||||
public void ValuesBetweenBounds(int value, int min, int max, BoundsInclusivity inclusivity, bool expected)
|
||||
{
|
||||
var result = MathUtility.IsBetween(value, min, max, inclusivity);
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(BetweenValuesFloat))]
|
||||
public void ValuesBetweenBoundsFloat(float value, float min, float max, BoundsInclusivity inclusivity, bool expected)
|
||||
{
|
||||
var result = MathUtility.IsBetween(value, min, max, inclusivity);
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fcf35728772424c9c2c84e29290ac5e
|
||||
timeCreated: 1780244918
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BracerLib.Utility;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BracerLib.Tests.Utility
|
||||
{
|
||||
public class TriangulatorTest : TestBase
|
||||
{
|
||||
private static IEnumerable AreaValueTests()
|
||||
{
|
||||
var points = new List<Vector2>();
|
||||
|
||||
yield return new TestCaseData(points.ToArray(), 0f).SetName("No points to calculate");
|
||||
|
||||
points.AddRange(new[]
|
||||
{
|
||||
Vector2.one,
|
||||
new Vector2(5f, 7f),
|
||||
new Vector2(-2f, 8f)
|
||||
});
|
||||
|
||||
yield return new TestCaseData(points.ToArray(), 23f).SetName("Area of a triangle");
|
||||
|
||||
points.Add(new Vector2(3f, 5f));
|
||||
|
||||
yield return new TestCaseData(points.ToArray(), 10f).SetName("Area of a quad");
|
||||
}
|
||||
|
||||
private static IEnumerable InsideTrianglePointsTest()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
Vector2.zero,
|
||||
new Vector2(3f, 3f),
|
||||
new Vector2(3f, 0f),
|
||||
new Vector2(2f, 1f),
|
||||
true
|
||||
).SetName("Point inside triangle");
|
||||
|
||||
yield return new TestCaseData(
|
||||
Vector2.zero,
|
||||
new Vector2(3f, 3f),
|
||||
new Vector2(3f, 0f),
|
||||
new Vector2(1f, 0f),
|
||||
true
|
||||
).SetName("Point on the line");
|
||||
|
||||
yield return new TestCaseData(
|
||||
Vector2.zero,
|
||||
new Vector2(3f, 3f),
|
||||
new Vector2(3f, 0f),
|
||||
new Vector2(3f, 0f),
|
||||
false
|
||||
).SetName("Point is one of triangle points");
|
||||
|
||||
yield return new TestCaseData(
|
||||
Vector2.zero,
|
||||
new Vector2(3f, 3f),
|
||||
new Vector2(3f, 0f),
|
||||
new Vector2(-2f, 1f),
|
||||
false
|
||||
).SetName("Point outside triangle");
|
||||
}
|
||||
|
||||
private static IEnumerable TriangulatePointsTest()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[]
|
||||
{
|
||||
Vector2.zero,
|
||||
Vector2.right,
|
||||
Vector2.right + Vector2.up,
|
||||
Vector2.up,
|
||||
Vector2.left + Vector2.up,
|
||||
Vector2.left
|
||||
},
|
||||
new[] { 5,3,2,2,0,5,5,4,3,2,1,0 }
|
||||
).SetName("Positive triangulation");
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[]
|
||||
{
|
||||
Vector2.zero,
|
||||
Vector2.right,
|
||||
Vector2.right + Vector2.down,
|
||||
Vector2.down,
|
||||
Vector2.left + Vector2.down,
|
||||
Vector2.left
|
||||
},
|
||||
new[] { 1,3,4,4,0,1,1,2,3,4,5,0 }
|
||||
).SetName("Negative triangulation");
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] { Vector2.zero, Vector2.right },
|
||||
Array.Empty<int>()
|
||||
).SetName("Empty triangulation");
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(AreaValueTests))]
|
||||
public void AreaCalculatesCorrectly(Vector2[] points, float expected)
|
||||
{
|
||||
var result = Triangulator.Area(points);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(InsideTrianglePointsTest))]
|
||||
public void PointsInsideTriangleCorrectly(Vector2 a, Vector2 b, Vector2 c, Vector2 p, bool expected)
|
||||
{
|
||||
var result = Triangulator.InsideTriangle(a, b, c, p);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(TriangulatePointsTest))]
|
||||
public void TriangulatePoints(Vector2[] points, int[] expected)
|
||||
{
|
||||
var result = Triangulator.Triangulate(points);
|
||||
Assert.AreEqual(expected.Length, result.Length);
|
||||
for (var i = 0; i < result.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(expected[i], result[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a7aaa2ed31747e5ae172f296a2808e3
|
||||
timeCreated: 1686834708
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efcb2963a18fd8646a6840e5596275f6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BracerLib.UI
|
||||
{
|
||||
/// A concrete subclass of the Unity UI `Graphic` class that just skips drawing.
|
||||
/// Useful for providing a raycast target without actually drawing anything.
|
||||
[ExcludeFromCoverage]
|
||||
public class NonDrawingGraphic : Graphic
|
||||
{
|
||||
public override void SetMaterialDirty() { }
|
||||
|
||||
public override void SetVerticesDirty() { }
|
||||
|
||||
/// Probably not necessary since the chain of calls `Rebuild()`->`UpdateGeometry()`->`DoMeshGeneration()`->`OnPopulateMesh()` won't happen; so here really just as a fail-safe.
|
||||
protected override void OnPopulateMesh(VertexHelper vh)
|
||||
{
|
||||
vh.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b78fc1af781b54e4392ff1040214c054
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace BracerLib.UI
|
||||
{
|
||||
public class SceneLoadingManager : MonoBehaviour
|
||||
{
|
||||
private static SceneLoadingManager _instance;
|
||||
|
||||
public static void LoadScenes(string[] scenePath, LoadSceneMode sceneMode)
|
||||
{
|
||||
if (!ValidInstance())
|
||||
return;
|
||||
|
||||
_instance.TryLoadScenes(scenePath, sceneMode);
|
||||
}
|
||||
|
||||
public static void Show()
|
||||
{
|
||||
if (ValidInstance())
|
||||
_instance.Toggle(true);
|
||||
}
|
||||
|
||||
public static void Hide()
|
||||
{
|
||||
if (ValidInstance())
|
||||
_instance.Toggle(false);
|
||||
}
|
||||
|
||||
private static bool ValidInstance()
|
||||
{
|
||||
if (_instance != null)
|
||||
return true;
|
||||
|
||||
Debug.LogError("No Load Screen Manager found.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private GameObject _loadingScreenGameObject;
|
||||
|
||||
private Coroutine _sceneLoadingCoroutine;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
DontDestroyOnLoad(_loadingScreenGameObject);
|
||||
if (_instance != null)
|
||||
{
|
||||
Debug.LogError("Multiple LoadScreenManagers found. Please delete one", this);
|
||||
Destroy(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
private void TryLoadScenes(string[] scenePaths, LoadSceneMode sceneMode)
|
||||
{
|
||||
if (_sceneLoadingCoroutine != null)
|
||||
{
|
||||
Debug.LogError("SceneLoadingManager is already loading scenes.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_sceneLoadingCoroutine = StartCoroutine(LoadSceneCoroutine(scenePaths, sceneMode));
|
||||
}
|
||||
|
||||
private IEnumerator LoadSceneCoroutine(string[] scenePaths, LoadSceneMode sceneMode)
|
||||
{
|
||||
Show();
|
||||
|
||||
for (var i = 0; i < scenePaths.Length; i++)
|
||||
yield return SceneManager.LoadSceneAsync(scenePaths[i], sceneMode);
|
||||
|
||||
Hide();
|
||||
_sceneLoadingCoroutine = null;
|
||||
}
|
||||
|
||||
private void Toggle(bool state)
|
||||
{
|
||||
_loadingScreenGameObject.SetActive(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e94d21e86e94de46b81f938aa50a29e
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d6041568ca07d940ae4a4f10cda4b8a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Utility
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
public static bool Contains<T>(this T[] array, T value) where T : IComparable
|
||||
{
|
||||
for (var i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (array[i].CompareTo(value) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Contains<T, U>(this T[] array, U value, Func<T, U, bool> predicate)
|
||||
{
|
||||
for (var i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (predicate(array[i], value))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static T[] Subset<T>(this T[] data, int index, int length)
|
||||
{
|
||||
var result = new T[length];
|
||||
Array.Copy(data, index, result, 0, length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool HasIndex(this IList list, int index) => index >= 0 && index < list.Count;
|
||||
|
||||
public static bool HasIndex(this Array array, int index) => index >= 0 && index < array.Length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fe12305b0d411b41bb552e80e21d2c5
|
||||
@@ -0,0 +1,31 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BracerLib.Utility
|
||||
{
|
||||
[ExcludeFromCoverage]
|
||||
public static class CameraUtility
|
||||
{
|
||||
private static Camera mainCamera;
|
||||
|
||||
public static Camera MainCamera
|
||||
{
|
||||
get
|
||||
{
|
||||
if (mainCamera == null)
|
||||
mainCamera = Camera.main;
|
||||
|
||||
return mainCamera;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 MouseToWorldPoint()
|
||||
{
|
||||
var screenPosition = (Vector3)Mouse.current.position.ReadValue();
|
||||
screenPosition.z = mainCamera.nearClipPlane;
|
||||
|
||||
return mainCamera.ScreenToWorldPoint(screenPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16fe30152b82df3439d6cd584b5603f7
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user