using System; using System.Collections.Generic; using System.Reflection; namespace UnityEngine.AddressableAssets.Initialization { /// /// Supports the evaluation of embedded runtime variables in addressables locations /// public static class AddressablesRuntimeProperties { // cache these to avoid GC allocations static Stack s_TokenStack = new Stack(32); static Stack s_TokenStartStack = new Stack(32); static bool s_StaticStacksAreInUse = false; #if !UNITY_EDITOR && UNITY_WSA_10_0 && ENABLE_DOTNET static Assembly[] GetAssemblies() { //Not supported on UWP platforms return new Assembly[0]; } #else static Assembly[] GetAssemblies() { return AppDomain.CurrentDomain.GetAssemblies(); } #endif static Dictionary s_CachedValues = new Dictionary(); internal static int GetCachedValueCount() { return s_CachedValues.Count; } /// /// Predefine a runtime property. /// /// The property name. /// The property value. public static void SetPropertyValue(string name, string val) { s_CachedValues[name] = val; } /// /// This will clear all PropertyValues that have been cached. This includes all values set by /// as well as any reflection-evaluated properties. /// public static void ClearCachedPropertyValues() { s_CachedValues.Clear(); } /// /// Evaluates a named property using cached values and static public fields and properties. Be aware that a field or property may be stripped if not referenced anywhere else. /// /// The property name. /// The value of the property. If not found, the name is returned. public static string EvaluateProperty(string name) { Debug.Assert(s_CachedValues != null, "ResourceManagerConfig.GetGlobalVar - s_cachedValues == null."); if (string.IsNullOrEmpty(name)) return string.Empty; string cachedValue; if (s_CachedValues.TryGetValue(name, out cachedValue)) return cachedValue; int i = name.LastIndexOf('.'); if (i < 0) return name; var className = name.Substring(0, i); var propName = name.Substring(i + 1); foreach (var a in GetAssemblies()) { Type t = a.GetType(className, false, false); if (t == null) continue; try { var pi = t.GetProperty(propName, BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.Public); if (pi != null) { var v = pi.GetValue(null, null); if (v != null) { s_CachedValues.Add(name, v.ToString()); return v.ToString(); } } var fi = t.GetField(propName, BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.Public); if (fi != null) { var v = fi.GetValue(null); if (v != null) { s_CachedValues.Add(name, v.ToString()); return v.ToString(); } } } catch (Exception) { // ignored } } return name; } /// /// Evaluates all tokens deliminated by '{' and '}' in a string and evaluates them with the EvaluateProperty method. /// /// The input string. /// The evaluated string after resolving all tokens. public static string EvaluateString(string input) { return EvaluateString(input, '{', '}', EvaluateProperty); } /// /// Evaluates all tokens deliminated by the specified delimiters in a string and evaluates them with the supplied method. /// /// The string to evaluate. /// The start token delimiter. /// The end token delimiter. /// Func that has a single string parameter and returns a string. /// The evaluated string. public static string EvaluateString(string inputString, char startDelimiter, char endDelimiter, Func varFunc) { if (string.IsNullOrEmpty(inputString)) return string.Empty; string originalString = inputString; Stack tokenStack; Stack tokenStartStack; if (!s_StaticStacksAreInUse) { tokenStack = s_TokenStack; tokenStartStack = s_TokenStartStack; s_StaticStacksAreInUse = true; } else { tokenStack = new Stack(32); tokenStartStack = new Stack(32); } tokenStack.Push(inputString); int popTokenAt = inputString.Length; char[] delimiters = {startDelimiter, endDelimiter}; bool delimitersMatch = startDelimiter == endDelimiter; int i = inputString.IndexOf(startDelimiter); int prevIndex = -2; while (i >= 0) { char c = inputString[i]; if (c == startDelimiter && (!delimitersMatch || tokenStartStack.Count == 0)) { tokenStartStack.Push(i); i++; } else if (c == endDelimiter && tokenStartStack.Count > 0) { int start = tokenStartStack.Peek(); string token = inputString.Substring(start + 1, i - start - 1); string tokenVal; if (popTokenAt <= i) { tokenStack.Pop(); } // check if the token is already included if (tokenStack.Contains(token)) tokenVal = "#ERROR-CyclicToken#"; else { tokenVal = varFunc == null ? string.Empty : varFunc(token); tokenStack.Push(token); } i = tokenStartStack.Pop(); popTokenAt = i + tokenVal.Length + 1; if (i > 0) { int rhsStartIndex = i + token.Length + 2; if (rhsStartIndex == inputString.Length) inputString = inputString.Substring(0, i) + tokenVal; else inputString = inputString.Substring(0, i) + tokenVal + inputString.Substring(rhsStartIndex); } else inputString = tokenVal + inputString.Substring(i + token.Length + 2); } bool infiniteLoopDetected = prevIndex == i; if (infiniteLoopDetected) return "#ERROR-" + originalString +" contains unmatched delimiters#"; prevIndex = i; i = inputString.IndexOfAny(delimiters, i); } tokenStack.Clear(); tokenStartStack.Clear(); if (ReferenceEquals(tokenStack, s_TokenStack)) s_StaticStacksAreInUse = false; return inputString; } } }