More efficient AtomicTags (#12)

* - added assembly defintions, and unit tests
- improved AtomicTags in regards of #8, #9 and #10

* Fixes #11
- Added Equality Members (inclusive HashCode) for ScriptableObjectVariableBase

* removed Rider Plugins from git
This commit is contained in:
Oliver Biwer 2019-03-05 22:57:47 +01:00 committed by Adam Ramberg
parent 81209d83b5
commit 53d6adc07b
19 changed files with 393 additions and 59 deletions

3
.gitignore vendored
View File

@ -7,6 +7,9 @@
# Visual Studio 2015 cache directory # Visual Studio 2015 cache directory
/.vs/ /.vs/
/.idea/
/Assets/Plugins/Editor/JetBrains*
# Autogenerated VS/MD/Consulo solution and project files # Autogenerated VS/MD/Consulo solution and project files
ExportedObj/ ExportedObj/

8
Assets/Plugins.meta Normal file
View File

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

View File

@ -1,10 +1,90 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine; using UnityEngine;
namespace UnityAtoms namespace UnityAtoms
{ {
public class AtomicTags : MonoBehaviour public class AtomicTags : MonoBehaviour
{ {
public List<StringConstant> Tags; [SerializeField] private List<StringConstant> tags = new List<StringConstant>();
/// <summary>
/// An Immutable, Sorted version of the Tags
/// </summary>
public StringConstant[] Tags { get; private set; }
private static Dictionary<string, List<GameObject>> taggedGOs = new Dictionary<string, List<GameObject>>();
private static Dictionary<GameObject, AtomicTags> instances = new Dictionary<GameObject, AtomicTags>();
public void AddTag(StringConstant tag) {
tags.Add(tag);
tags.Sort((x, y) => String.Compare(x.Value, y.Value, StringComparison.Ordinal));
Tags = tags.ToArray();
}
public void RemoveTag(string tag) {
for (var i = tags.Count - 1; i >= 0; i--) {
if (tags[i].Value == tag) {
tags.RemoveAt(i);
}
}
tags.Sort((x, y) => String.Compare(x.Value, y.Value, StringComparison.Ordinal));
Tags = tags.ToArray();
}
private void OnValidate() {
tags.Sort((x, y) => String.Compare(x.Value, y.Value, StringComparison.Ordinal));
}
private void OnEnable() {
tags.Sort((x, y) => String.Compare(x.Value, y.Value, StringComparison.Ordinal));
Tags = tags.ToArray();
if(! instances.ContainsKey(this.gameObject)) instances.Add(this.gameObject, this);
foreach (var stringConstant in tags) {
var tag = stringConstant.Value;
if(! taggedGOs.ContainsKey(tag)) taggedGOs.Add(tag, new List<GameObject>());
taggedGOs[tag].Add(this.gameObject);
};
}
private void OnDisable(){
if(instances.ContainsKey(this.gameObject)) instances.Remove(this.gameObject);
foreach (var stringConstant in tags) {
var tag = stringConstant.Value;
if(taggedGOs.ContainsKey(tag)) taggedGOs[tag].Remove(this.gameObject);
};
}
/// <summary>
/// Faster alternative to go.GetComponent&lt;AtomicTags&gt;() since they are already cached in a dictionary
/// </summary>
/// <returns>
/// - null if the GameObject does not have AtomicTags or they (or the GO) are disabled
/// - the AtomicTag component
/// </returns>
public static AtomicTags GetForGameObject(GameObject go) {
if (!instances.ContainsKey(go)) return null;
return instances[go];
}
/// <summary>
/// Retrieves all AtomicTags for a given GameObject
/// </summary>
/// <returns>
/// - null if the GameObject does not have AtomicTags or they (or the GO) are disabled
/// - an array of strings containing the tags
/// </returns>
public static string[] GetAtomicTags(GameObject go) {
if (!instances.ContainsKey(go)) return null;
var atomicTags = instances[go];
string[] result = new string[atomicTags.tags.Count];
for (var i = 0; i < result.Length; i++) {
result[i] = atomicTags.tags[i].Value;
}
return result;
}
} }
} }

View File

@ -9,9 +9,29 @@ namespace UnityAtoms
[Multiline] [Multiline]
public string DeveloperDescription = ""; public string DeveloperDescription = "";
public virtual T Value { get { return value; } set { } } public virtual T Value { get { return value; } set { throw new NotImplementedException(); } }
[SerializeField] [SerializeField]
protected T value; protected T value;
protected bool Equals(ScriptableObjectVariableBase<T> other) {
return EqualityComparer<T>.Default.Equals(value, other.value);
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((ScriptableObjectVariableBase<T>) obj);
}
public override int GetHashCode() {
unchecked {
return EqualityComparer<T>.Default.GetHashCode(value);
}
}
public static bool operator ==(ScriptableObjectVariableBase<T> left, ScriptableObjectVariableBase<T> right) { return Equals(left, right); }
public static bool operator !=(ScriptableObjectVariableBase<T> left, ScriptableObjectVariableBase<T> right) { return !Equals(left, right); }
} }
} }

View File

@ -0,0 +1,16 @@
{
"name": "UnityAtomsEditor",
"references": [
"UnityAtoms"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

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

View File

@ -1,48 +1,103 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine; using UnityEngine;
namespace UnityAtoms namespace UnityAtoms
{ {
public static class GameObjectExtensions public static class GameObjectExtensions
{ {
public static List<StringConstant> GetTags(this GameObject go) #region AtomicTagExtensions
{
return go.GetComponent<AtomicTags>() ? go.GetComponent<AtomicTags>().Tags : null; /// <summary>
} /// Retrieves all AtomicTags for a given GameObject
/// </summary>
public static bool HasTag(this GameObject go, string str) /// <returns>
{ /// - null if the GameObject does not have Atomic Tags or they (or the GO) are disabled)
var tags = go.GetComponent<AtomicTags>() ? go.GetComponent<AtomicTags>().Tags : null; /// - an array of strings containing the tags
/// </returns>
for (int i = 0; tags != null && i < tags.Count; ++i) public static string[] GetAtomicTags(this GameObject go) {
{ return AtomicTags.GetAtomicTags(go);
if (tags[i].Value == str)
{
return true;
}
} }
return false; /// <returns>
} /// - False if the GameObject does not have the AtomicTag, else True
/// </returns>
public static bool HasTag(this GameObject go, string str) {
var atomicTags = AtomicTags.GetForGameObject(go);
if (atomicTags == null) return false;
public static bool HasTag(this GameObject go, StringConstant stringConstant) var tags = atomicTags.Tags;
{ for (int i = 0; tags != null && i < tags.Length; ++i)
return go.HasTag(stringConstant.Value);
}
public static bool HasAnyTag(this GameObject go, List<StringConstant> stringConstants)
{
for (int i = 0; stringConstants != null && i < stringConstants.Count; ++i)
{
if (go.HasTag(stringConstants[i].Value))
{ {
return true; if (tags[i].Value == str)
{
return true;
}
} }
return false;
} }
/// <returns>
/// - False if the GameObject does not have the AtomicTag, else True
/// </returns>
public static bool HasTag(this GameObject go, StringConstant stringConstant)
{
return go.HasTag(stringConstant.Value);
}
public static bool HasAnyTag(this GameObject go, List<string> strings)
{
var atomicTags = AtomicTags.GetForGameObject(go);
if (atomicTags == null) return false;
return false; strings.Sort();
} var tags = atomicTags.Tags;
// this makes use of Tags being always sorted
// instead of O(n*m) this is worst case: O(n + m) +(because of the sort) O(n * log(n))
// O(n*m) ~= O(n^2) <-> O(n+m)+O(n * log(n)) ~= O(3n * log(n))
for (int i = 0, j = 0; i < strings.Count && j < tags.Length;) {
var x = String.CompareOrdinal(strings[i], tags[j].Value);
if (x == 0) {
return true;
}
if (x > 0) {
++j;
} else {
++i;
}
}
return false;
}
public static bool HasAnyTag(this GameObject go, List<StringConstant> stringConstants)
{
// basically same method as above, the code is mostly copy and pasted because its not preferable to convert
// stringconstants to strings and calling the other method, because of memory allocation
var atomicTags = AtomicTags.GetForGameObject(go);
if (atomicTags == null) return false;
stringConstants.Sort((x, y) => String.Compare(x.Value, y.Value, StringComparison.Ordinal));
var tags = atomicTags.Tags;
for (int i = 0, j = 0; i < stringConstants.Count && j < tags.Length;) {
var x = String.CompareOrdinal(stringConstants[i].Value, tags[j].Value);
if (x == 0) {
return true;
}
if (x > 0) {
++j;
} else {
++i;
}
}
return false;
}
#endregion
} }
} }

View File

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

View File

@ -0,0 +1,55 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using UnityAtoms;
using UnityEngine;
using UnityEngine.TestTools;
namespace Tests
{
public class AtomicTagTests
{
[UnityTest]
public IEnumerator AtomicTagTests_HasAnyTagTest(){
var go = new GameObject();
var atomicTags = go.AddComponent<AtomicTags>();
string[] random_tags_raw = new[] {"a", "c", "e", "g", "i", "k", "m"};
var random_tags_shuffled = random_tags_raw.OrderBy((s => Random.value)).ToList();
var fieldinfo = typeof(StringConstant).GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
for (int i = 0; i < random_tags_shuffled.Count; ++i) {
var stringConstant = ScriptableObject.CreateInstance<StringConstant>();
fieldinfo.SetValue(stringConstant, random_tags_shuffled[i]);
atomicTags.AddTag(stringConstant);
}
yield return null;
yield return new WaitForFixedUpdate();
Assert.NotNull(atomicTags);
Assert.NotNull(atomicTags.Tags);
// check if the tags are actually sorted:
// Debug.Log(atomicTags.Tags.Select(t => t.Value).Aggregate((a, b) => a + ", " + b));
{
int i = 0;
foreach (var atomicTagsTag in atomicTags.Tags) {
Assert.AreEqual(random_tags_raw[i], atomicTagsTag.Value);
++i;
}
}
Assert.IsFalse(go.HasAnyTag(new List<string>() {"b", "d", "f", "h", "j", "l"}));
Assert.IsTrue(go.HasAnyTag(new List<string>() {"b", "d", "f", "h", "j", "m"}));
}
}
}

View File

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

View File

@ -0,0 +1,32 @@
using System.Collections;
using System.Reflection;
using NUnit.Framework;
using UnityAtoms;
using UnityEngine;
using UnityEngine.TestTools;
namespace Tests {
public class ScriptableObjectBaseTest {
[Test]
public void ScriptableObjectBaseTest_EqualityMembers() {
var fieldinfo = typeof(StringConstant).GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
var stringConstant = ScriptableObject.CreateInstance<StringConstant>();
fieldinfo.SetValue(stringConstant, "some constant string");
var stringConstant2 = ScriptableObject.CreateInstance<StringConstant>();
fieldinfo.SetValue(stringConstant2, "some constant string");
var stringConstant3 = ScriptableObject.CreateInstance<StringConstant>();
fieldinfo.SetValue(stringConstant3, "some other string");
Assert.AreEqual("some constant string".GetHashCode(), stringConstant.Value.GetHashCode());
Assert.AreEqual("some constant string".GetHashCode(), stringConstant.GetHashCode());
Assert.AreEqual(stringConstant2, stringConstant);
Assert.IsTrue(stringConstant2 == stringConstant);
Assert.IsFalse(stringConstant3 == stringConstant);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d737766b78a458fb94e1d3df4f581a8
timeCreated: 1551103675

View File

@ -0,0 +1,16 @@
{
"name": "Tests",
"references": [
"UnityAtoms"
],
"optionalUnityReferences": [
"TestAssemblies"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

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

View File

@ -0,0 +1,3 @@
{
"name": "UnityAtoms"
}

View File

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

View File

@ -1,10 +1,11 @@
{ {
"dependencies": { "dependencies": {
"com.unity.ads": "2.0.8", "com.unity.ads": "2.0.8",
"com.unity.analytics": "2.0.16", "com.unity.analytics": "3.2.2",
"com.unity.package-manager-ui": "1.9.11", "com.unity.collab-proxy": "1.2.15",
"com.unity.package-manager-ui": "2.0.3",
"com.unity.purchasing": "2.0.3", "com.unity.purchasing": "2.0.3",
"com.unity.textmeshpro": "1.2.4", "com.unity.textmeshpro": "1.3.0",
"com.unity.modules.ai": "1.0.0", "com.unity.modules.ai": "1.0.0",
"com.unity.modules.animation": "1.0.0", "com.unity.modules.animation": "1.0.0",
"com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.assetbundle": "1.0.0",

View File

@ -1,21 +1,23 @@
%YAML 1.1 %YAML 1.1
%TAG !u! tag:unity3d.com,2011: %TAG !u! tag:unity3d.com,2011:
--- !u!159 &1 --- !u!159 &1
EditorSettings: EditorSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 7 serializedVersion: 7
m_ExternalVersionControlSupport: Visible Meta Files m_ExternalVersionControlSupport: Visible Meta Files
m_SerializationMode: 2 m_SerializationMode: 2
m_LineEndingsForNewScripts: 2 m_LineEndingsForNewScripts: 2
m_DefaultBehaviorMode: 1 m_DefaultBehaviorMode: 1
m_SpritePackerMode: 4 m_PrefabRegularEnvironment: {fileID: 0}
m_SpritePackerPaddingPower: 1 m_PrefabUIEnvironment: {fileID: 0}
m_EtcTextureCompressorBehavior: 1 m_SpritePackerMode: 4
m_EtcTextureFastCompressor: 1 m_SpritePackerPaddingPower: 1
m_EtcTextureNormalCompressor: 2 m_EtcTextureCompressorBehavior: 1
m_EtcTextureBestCompressor: 4 m_EtcTextureFastCompressor: 1
m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd m_EtcTextureNormalCompressor: 2
m_ProjectGenerationRootNamespace: m_EtcTextureBestCompressor: 4
m_UserGeneratedProjectSuffix: m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef
m_CollabEditorSettings: m_ProjectGenerationRootNamespace:
inProgressEnabled: 1 m_CollabEditorSettings:
inProgressEnabled: 1
m_EnableTextureStreamingInPlayMode: 1

View File

@ -1 +1 @@
m_EditorVersion: 2018.2.14f1 m_EditorVersion: 2018.3.3f1