init
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using RPGCoreCommon.Helpers.CustomTypes;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
[Serializable]
|
||||
public class DynamicValue
|
||||
{
|
||||
// User defined formula or text with markers
|
||||
[SerializeField] internal string _serializedFormula;
|
||||
[SerializeField] internal SerializableDictionary<string, SerializableType> _serializedTypes = new();
|
||||
[SerializeField] internal SerializableDictionary<string, Object> _serializedSources = new();
|
||||
|
||||
private bool _isParsed;
|
||||
private string _formulaFormat;
|
||||
private MarkerParser[] _markers;
|
||||
|
||||
private void Parse()
|
||||
{
|
||||
if (Application.isPlaying && _isParsed) return;
|
||||
|
||||
_isParsed = true;
|
||||
|
||||
var matches = new Regex("{[a-zA-z0-9-\\.]*}").Matches(_serializedFormula);
|
||||
_markers = new MarkerParser[matches.Count];
|
||||
_formulaFormat = _serializedFormula;
|
||||
|
||||
for (var i = matches.Count-1; i >= 0; i--)
|
||||
{
|
||||
_markers[i] = new MarkerParser(matches[i].Value);
|
||||
_markers[i].sourceType = GetSourceType(_markers[i].sourceMarker);
|
||||
_formulaFormat = _formulaFormat.Remove(matches[i].Index, matches[i].Length).Insert(matches[i].Index, $"{{{i}}}");
|
||||
}
|
||||
}
|
||||
|
||||
private Type GetSourceType(string sourceMarker)
|
||||
{
|
||||
if (_serializedTypes.TryGetValue(sourceMarker, out var type)) return type;
|
||||
if (_serializedSources.TryGetValue(sourceMarker, out var obj)) return obj.GetType();
|
||||
return null;
|
||||
}
|
||||
|
||||
private object GetSource(string sourceMarker, DynamicValueSourceContext sourceContext = null)
|
||||
{
|
||||
if (_serializedSources.TryGetValue(sourceMarker, out var s1)) return s1;
|
||||
if (sourceContext != null && sourceContext.TryGetSource(sourceMarker, out var s2)) return s2;
|
||||
return null;
|
||||
}
|
||||
|
||||
public void RemoveDynamicTypes()
|
||||
{
|
||||
_serializedTypes.Clear();
|
||||
}
|
||||
|
||||
public void SetDynamicTypes(Dictionary<string, Type> types)
|
||||
{
|
||||
RemoveDynamicTypes();
|
||||
types.DictForEach(SetDynamicType);
|
||||
}
|
||||
|
||||
public void SetDynamicType(string name, Type type)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Debug.LogError($"method '{nameof(SetDynamicType)}' is only available in editor. Dynamic types should not be changed runtime!");
|
||||
return;
|
||||
}
|
||||
|
||||
_serializedTypes[name] = new SerializableType(type);
|
||||
}
|
||||
|
||||
public MarkerParser[] GetMarkers()
|
||||
{
|
||||
Parse();
|
||||
return _markers.ToArray();
|
||||
}
|
||||
|
||||
public string GetString(DynamicValueSourceContext sourceContext = null)
|
||||
{
|
||||
Parse();
|
||||
var values = _markers
|
||||
.Select(marker => marker.GetValue(GetSource(marker.sourceMarker, sourceContext)))
|
||||
.ToArray<object>();
|
||||
return string.Format(_formulaFormat, values);
|
||||
}
|
||||
|
||||
public int GetValue(DynamicValueSourceContext sourceContext = null)
|
||||
{
|
||||
Parse();
|
||||
ExpressionEvaluator.Evaluate(GetString(sourceContext), out int value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3e7897496b64571880ab2ffe679fa43
|
||||
timeCreated: 1759334381
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DynamicValueAttribute : PropertyAttribute
|
||||
{
|
||||
internal DynamicValueType type { private set; get; }
|
||||
internal List<Type> staticTypes { private set; get; }
|
||||
|
||||
internal List<Type> ignoredDeclaringTypes { private set; get; } = new()
|
||||
{
|
||||
typeof(Object),
|
||||
typeof(Behaviour),
|
||||
typeof(MonoBehaviour),
|
||||
typeof(Component)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DynamicValue"/> will be managed via code (adding/removing in inspector is)
|
||||
/// </summary>
|
||||
public DynamicValueAttribute(DynamicValueType type = DynamicValueType.BySerializedSources, params Type[] staticTypes)
|
||||
{
|
||||
this.type = type;
|
||||
if (type is DynamicValueType.ByStaticTypes) this.staticTypes = staticTypes.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0eb1b79e28da4cff8d0554fd6b270f26
|
||||
timeCreated: 1759335563
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public class DynamicValueExistingTypeException : UnityException
|
||||
{
|
||||
public DynamicValueExistingTypeException(string text) : base(text)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40c9343cc5534008aecf4a1734171915
|
||||
timeCreated: 1759685699
|
||||
@@ -0,0 +1,11 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public class DynamicValueInvalidMarkerException : UnityException
|
||||
{
|
||||
public DynamicValueInvalidMarkerException(string text) : base(text)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 902c960012c84504a20e452871dadc15
|
||||
timeCreated: 1760275028
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method)]
|
||||
public class DynamicValueProviderAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// <b>[Fields/Property]</b><br/>
|
||||
/// Marks non-public field or property to provide value for <see cref="DynamicValue"/>'s formula.
|
||||
/// Public fields and properties are always used as value providers.
|
||||
/// Collections are ALWAYS ignored - if you want to get values from collection try to use custom method. More info below.
|
||||
/// <br/><br/>
|
||||
/// <b>[Method]</b><br/>
|
||||
/// Method can be marked as custom value provider.
|
||||
/// Those methods can have any name, but require specific return value and one parameter.
|
||||
/// Only two definitions of methods are valid, ex.:
|
||||
/// <ul>
|
||||
/// <li><b>T CustomProviderMethod(Type)</b> - this usage supports polymorphism,
|
||||
/// type in parameter will be supplied with type of any class that implements <T>.
|
||||
/// Thanks to this type of value we want can be specified in marker.</li>
|
||||
/// <li><b>T CustomProviderMethod(string)</b> - this usage DOESN'T support polymorphism,
|
||||
/// string in parameter is exactly value specified in marker.</li>
|
||||
/// </ul>
|
||||
/// </summary>
|
||||
public DynamicValueProviderAttribute()
|
||||
{
|
||||
// TODO: tutaj jeszcze jako argument trzeba przekazać nazwę drugiej metody, która ma listę możliwych stringów w przypadku użycia na CustomProviderMethod(string), wtedy będzie można to fajnie walidować już na poziomie tego narzędzia
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b49ba0d446154c368a559d69b8edf97b
|
||||
timeCreated: 1763033238
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public class DynamicValueSourceContext
|
||||
{
|
||||
private readonly Dictionary<string, object> _sources = new();
|
||||
|
||||
public void SetSource(string name, object source)
|
||||
{
|
||||
_sources.Remove(name.ToLower());
|
||||
_sources.Add(name.ToLower(), source);
|
||||
}
|
||||
|
||||
public void RemoveResource(string name)
|
||||
{
|
||||
_sources.Remove(name.ToLower());
|
||||
}
|
||||
|
||||
public void RemoveSources()
|
||||
{
|
||||
_sources.Clear();
|
||||
}
|
||||
|
||||
public bool TryGetSource(string name, out object source)
|
||||
{
|
||||
return _sources.TryGetValue(name.ToLower(), out source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7ffbb14acb7403a982719f0771d95f4
|
||||
timeCreated: 1763657503
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public enum DynamicValueType
|
||||
{
|
||||
/// <summary>
|
||||
/// [Default option] Sources defined by user in editor.
|
||||
/// </summary>
|
||||
BySerializedSources,
|
||||
|
||||
/// <summary>
|
||||
/// Types of sources defined runtime.
|
||||
/// Usually when other field can define what types will be used.
|
||||
/// Sources needs to be added later in runtime.
|
||||
/// </summary>
|
||||
ByDynamicTypes,
|
||||
|
||||
/// <summary>
|
||||
/// Types of sources defined via attribute.
|
||||
/// Sources needs to be added later in runtime.
|
||||
/// </summary>
|
||||
ByStaticTypes
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc2e7b666a654564ae758ccb7c7b2571
|
||||
timeCreated: 1762786038
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("RPGCoreCommon.DynamicValues.Editor")]
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77ee6ce56165430e8dbf117a46d1182f
|
||||
timeCreated: 1759337937
|
||||
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using RPGCoreCommon.Helpers.Exceptions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public class MarkerParser
|
||||
{
|
||||
private static readonly BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
|
||||
|
||||
public string wholeMarker { get; private set; }
|
||||
public string sourceMarker { get; private set; }
|
||||
|
||||
internal Type sourceType;
|
||||
private MemberInfo[] _members;
|
||||
private object[] _membersParameters;
|
||||
private string[] _markers;
|
||||
private string[] _parameters;
|
||||
|
||||
internal MarkerParser(string wholeMarker)
|
||||
{
|
||||
this.wholeMarker = wholeMarker;
|
||||
var tempMarker = wholeMarker.Trim('{', '}').Split('.');
|
||||
|
||||
// Source marker should be always present (even if not it will be as empty string)
|
||||
sourceMarker = tempMarker[0];
|
||||
|
||||
if (tempMarker.Length <= 1) return;
|
||||
|
||||
_markers = new string[tempMarker.Length - 1];
|
||||
_parameters = new string[tempMarker.Length - 1];
|
||||
|
||||
for (var i = 1; i < tempMarker.Length; i++)
|
||||
{
|
||||
var valueMarker = tempMarker[i];
|
||||
var tempValueMarker = valueMarker.TrimEnd(']').Split('[');
|
||||
_markers[i - 1] = tempValueMarker[0];
|
||||
if (tempValueMarker.Length > 1) _parameters[i - 1] = tempValueMarker[1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="type">Type that will be scanned</param>
|
||||
/// <param name="attribute">Attribute with settings for this instance of <see cref="DynamicValue"/></param>
|
||||
/// <returns>List of members that can be used as value providers for <see cref="DynamicValue"/>'s formula</returns>
|
||||
internal static List<MemberInfo> GetMembersForType(Type type, DynamicValueAttribute attribute)
|
||||
{
|
||||
return type.GetAllTypes()
|
||||
.SelectMany(t => t.GetMembers(BindingFlags))
|
||||
.Where(member =>
|
||||
{
|
||||
if (attribute.ignoredDeclaringTypes.Contains(member.DeclaringType)) return false;
|
||||
if (member is not MethodInfo and not PropertyInfo and not FieldInfo) return false;
|
||||
var isProvider = member.GetCustomAttribute<DynamicValueProviderAttribute>() != null;
|
||||
return isProvider || (member.IsMemberReadPublic() && member is not MethodInfo);
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <param name="member">Member to check if it can provide any subtypes besides itself</param>
|
||||
/// <param name="isProvider">TRUE whenever given member is provider for <see cref="DynamicValue"/>'s formula</param>
|
||||
/// <param name="isDynamic">TRUE if this member has parameter, but not specified with type, only by user's string</param>
|
||||
/// <returns>All subtypes that this member can provide. If type can't be specified then it is dynamic.</returns>
|
||||
internal static List<Type> GetSubTypesInProvider(MemberInfo member, out bool isProvider, out bool isDynamic)
|
||||
{
|
||||
isProvider = true;
|
||||
isDynamic = false;
|
||||
var providerAttribute = member.GetCustomAttribute<DynamicValueProviderAttribute>();
|
||||
var methodInfo = member as MethodInfo;
|
||||
|
||||
// Only Methods support parameters, but only when attribute is also given
|
||||
if (methodInfo == null || providerAttribute == null)
|
||||
{
|
||||
isProvider = false;
|
||||
return new List<Type>();
|
||||
}
|
||||
|
||||
var parameter = methodInfo.GetParameters().FirstOrDefault();
|
||||
var isStringParameter = parameter?.ParameterType == typeof(string);
|
||||
var isTypeParameter = parameter?.ParameterType == typeof(Type);
|
||||
|
||||
// BAD - invalid parameter definition in method!
|
||||
if ((!isStringParameter && !isTypeParameter) || methodInfo.GetParameters().Length != 1)
|
||||
{
|
||||
Debug.LogError($"<b>{member.Name}</b> has invalid definition!<br>" +
|
||||
$"[{nameof(DynamicValueProviderAttribute)}] Supports only methods with ONE parameter that is <b>string</b> or <b>type</b>!");
|
||||
return new List<Type>();
|
||||
}
|
||||
|
||||
// BAD - return type is required!
|
||||
if (methodInfo.ReturnType == typeof(void))
|
||||
{
|
||||
Debug.LogError($"<b>{member.Name}</b> has invalid definition!<br>" +
|
||||
$"[{nameof(DynamicValueProviderAttribute)}] Supports only methods with defined return type!");
|
||||
return new List<Type>();
|
||||
}
|
||||
|
||||
// GOOD by string - polymorphism not supported - parameter is dynamic, which means text can have anything
|
||||
if (isStringParameter)
|
||||
{
|
||||
isDynamic = true;
|
||||
return new List<Type> { methodInfo.ReturnType };
|
||||
}
|
||||
|
||||
// GOOD by type - polymorphism supported
|
||||
return UnityEditor.TypeCache.GetTypesDerivedFrom(methodInfo.ReturnType)
|
||||
.Where(type => !type.IsAbstract && !type.IsGenericType)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <returns>Type of value that this marker leads to</returns>
|
||||
/// <exception cref="MemberNotFoundException">When member at path could not be found</exception>
|
||||
public Type GetValueType()
|
||||
{
|
||||
return GetMembers().Last().GetMemberValueType();
|
||||
}
|
||||
|
||||
public string GetValue(object source)
|
||||
{
|
||||
// This will also refill _members and _membersParameters
|
||||
GetMembers();
|
||||
|
||||
var tempObject = source;
|
||||
for (var i = 0; i < _members.Length; i++)
|
||||
{
|
||||
if (_members[i] is FieldInfo or PropertyInfo)
|
||||
tempObject = _members[i].GetMemberValue(tempObject);
|
||||
else if (_members[i] is MethodInfo methodInfo)
|
||||
tempObject = methodInfo.Invoke(tempObject, new[] { _membersParameters[i] });
|
||||
}
|
||||
|
||||
return tempObject.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns chain of <see cref="MemberInfo"/> that will be used to get value for this marker.
|
||||
/// </summary>
|
||||
/// <returns>Array of <see cref="MemberInfo"/> used by this marker.</returns>
|
||||
/// <exception cref="MemberNotFoundException">When given path from marker doesn't exist in source.</exception>
|
||||
public MemberInfo[] GetMembers()
|
||||
{
|
||||
if (Application.isPlaying && _members != null) return _members.ToArray();
|
||||
|
||||
_members = new MemberInfo[_markers.Length];
|
||||
_membersParameters = new object[_markers.Length];
|
||||
|
||||
var tempType = sourceType;
|
||||
for (var i = 0; i < _markers.Length; i++)
|
||||
{
|
||||
var member = tempType.GetAllTypes()
|
||||
.SelectMany(t => t.GetMember(_markers[i], BindingFlags)).FirstOrDefault();
|
||||
|
||||
// BAD - whole member doesn't exist
|
||||
if (member == null)
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] is not found!", tempType);
|
||||
|
||||
var attribute = member.GetCustomAttribute<DynamicValueProviderAttribute>();
|
||||
|
||||
// BAD - non-public member without attribute
|
||||
if (!member.IsMemberReadPublic() && attribute is null)
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but its non-public without {nameof(DynamicValueProviderAttribute)}!", tempType);
|
||||
|
||||
// BAD - member is collection, collections aren't supported
|
||||
if (typeof(Enumerable).IsAssignableFrom(member.GetMemberValueType()))
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but it is collection. Collections aren't supported!", tempType);
|
||||
|
||||
tempType = member.GetMemberValueType();
|
||||
|
||||
if (member is not MethodInfo methodInfo)
|
||||
{
|
||||
// BAD - simple non-collection found, but marker has parameter
|
||||
if (_parameters[i] != null)
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but marker has parameter. Only methods can have parameters!", tempType);
|
||||
|
||||
// GOOD - simple non-collection field or property
|
||||
_members[i] = member;
|
||||
continue;
|
||||
}
|
||||
|
||||
// BAD - is method, but without required attribute
|
||||
if (attribute is null)
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but without required {nameof(DynamicValueProviderAttribute)}!", tempType);
|
||||
|
||||
var validReturnType = methodInfo.ReturnType != typeof(void);
|
||||
var parameter = methodInfo.GetParameters().FirstOrDefault();
|
||||
var isStringParameter = parameter?.ParameterType == typeof(string);
|
||||
var isTypeParameter = parameter?.ParameterType == typeof(Type);
|
||||
|
||||
// BAD - provider method has invalid definition
|
||||
if (!validReturnType || (!isStringParameter && !isTypeParameter))
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but definition is invalid! Must contain only one parameter <b>string</b> or <b>Type</b> and also must return something.", tempType);
|
||||
|
||||
_members[i] = methodInfo;
|
||||
|
||||
// GOOD - parameter is string, which means it is dynamic, only body of method can use it.
|
||||
// Polymorphism is NOT supported here, return value is always method's return type
|
||||
if (isStringParameter)
|
||||
{
|
||||
_membersParameters[i] = _parameters[i];
|
||||
tempType = methodInfo.ReturnType;
|
||||
continue;
|
||||
}
|
||||
|
||||
// GOOD - parameter is Type, which we can match by its name.
|
||||
// Polymorphism is supported here, dynamic value know type referenced by marker.
|
||||
tempType = UnityEditor.TypeCache.GetTypesDerivedFrom(methodInfo.ReturnType)
|
||||
.FirstOrDefault(t => t.Name.Equals(_parameters[i], StringComparison.OrdinalIgnoreCase));
|
||||
_membersParameters[i] = tempType;
|
||||
}
|
||||
|
||||
return _members.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="GetMembers()"/>
|
||||
/// </summary>
|
||||
/// <param name="membersParameters">
|
||||
/// <b>[Only when MethodInfo]</b> List of parameters that will be used for specific member.
|
||||
/// Can be <b>string</b> or <b>Type</b>.
|
||||
/// </param>
|
||||
/// <inheritdoc cref="GetMembers()"/>
|
||||
public MemberInfo[] GetMembers(out object[] membersParameters)
|
||||
{
|
||||
var members = GetMembers();
|
||||
membersParameters = _membersParameters.ToArray();
|
||||
return members;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99a9d00853004ff788e659a257298db0
|
||||
timeCreated: 1763109073
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.DynamicValues",
|
||||
"rootNamespace": "RPGCoreCommon.DynamicValues",
|
||||
"references": [
|
||||
"RPGCoreCommon.Helpers"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ade6d5973db3446ab20f0bb7e4a872f8
|
||||
timeCreated: 1759334847
|
||||
Reference in New Issue
Block a user