A game about forced loneliness, made by TACStudios
1using System; 2using Unity.VisualScripting.Dependencies.NCalc; 3using UnityEngine; 4using NCalc = Unity.VisualScripting.Dependencies.NCalc.Expression; 5 6namespace Unity.VisualScripting 7{ 8 /// <summary> 9 /// Evaluates a mathematical or logical formula with multiple arguments. 10 /// </summary> 11 public sealed class Formula : MultiInputUnit<object> 12 { 13 [SerializeAs(nameof(Formula))] 14 private string _formula; 15 16 private NCalc ncalc; 17 18 /// <summary> 19 /// A mathematical or logical expression tree. 20 /// </summary> 21 [DoNotSerialize] 22 [Inspectable, UnitHeaderInspectable] 23 [InspectorTextArea] 24 public string formula 25 { 26 get => _formula; 27 set 28 { 29 _formula = value; 30 31 InitializeNCalc(); 32 } 33 } 34 35 /// <summary> 36 /// Whether input arguments should only be fetched once and then reused. 37 /// </summary> 38 [Serialize] 39 [Inspectable(order = int.MaxValue)] 40 [InspectorExpandTooltip] 41 public bool cacheArguments { get; set; } 42 43 /// <summary> 44 /// The result of the calculation or evaluation. 45 /// </summary> 46 [DoNotSerialize] 47 [PortLabelHidden] 48 public ValueOutput result { get; private set; } 49 50 protected override int minInputCount => 0; 51 52 protected override void Definition() 53 { 54 base.Definition(); 55 56 result = ValueOutput(nameof(result), Evaluate); 57 58 InputsAllowNull(); 59 60 foreach (var input in multiInputs) 61 { 62 Requirement(input, result); 63 } 64 65 InitializeNCalc(); 66 } 67 68 private void InitializeNCalc() 69 { 70 if (string.IsNullOrEmpty(formula)) 71 { 72 ncalc = null; 73 return; 74 } 75 76 ncalc = new NCalc(formula); 77 ncalc.Options = EvaluateOptions.IgnoreCase; 78 ncalc.EvaluateParameter += EvaluateTreeParameter; 79 ncalc.EvaluateFunction += EvaluateTreeFunction; 80 } 81 82 private object Evaluate(Flow flow) 83 { 84 if (ncalc == null) 85 { 86 throw new InvalidOperationException("No formula provided."); 87 } 88 89 ncalc.UpdateUnityTimeParameters(); 90 91 return ncalc.Evaluate(flow); 92 } 93 94 private void EvaluateTreeFunction(Flow flow, string name, FunctionArgs args) 95 { 96 if (name == "v2" || name == "V2") 97 { 98 if (args.Parameters.Length != 2) 99 { 100 throw new ArgumentException($"v2() takes at exactly 2 arguments. {args.Parameters.Length} provided."); 101 } 102 103 args.Result = new Vector2 104 ( 105 ConversionUtility.Convert<float>(args.Parameters[0].Evaluate(flow)), 106 ConversionUtility.Convert<float>(args.Parameters[1].Evaluate(flow)) 107 ); 108 } 109 else if (name == "v3" || name == "V3") 110 { 111 if (args.Parameters.Length != 3) 112 { 113 throw new ArgumentException($"v3() takes at exactly 3 arguments. {args.Parameters.Length} provided."); 114 } 115 116 args.Result = new Vector3 117 ( 118 ConversionUtility.Convert<float>(args.Parameters[0].Evaluate(flow)), 119 ConversionUtility.Convert<float>(args.Parameters[1].Evaluate(flow)), 120 ConversionUtility.Convert<float>(args.Parameters[2].Evaluate(flow)) 121 ); 122 } 123 else if (name == "v4" || name == "V4") 124 { 125 if (args.Parameters.Length != 4) 126 { 127 throw new ArgumentException($"v4() takes at exactly 4 arguments. {args.Parameters.Length} provided."); 128 } 129 130 args.Result = new Vector4 131 ( 132 ConversionUtility.Convert<float>(args.Parameters[0].Evaluate(flow)), 133 ConversionUtility.Convert<float>(args.Parameters[1].Evaluate(flow)), 134 ConversionUtility.Convert<float>(args.Parameters[2].Evaluate(flow)), 135 ConversionUtility.Convert<float>(args.Parameters[3].Evaluate(flow)) 136 ); 137 } 138 } 139 140 public object GetParameterValue(Flow flow, string name) 141 { 142 if (name.Length == 1) 143 { 144 var character = name[0]; 145 146 if (char.IsLetter(character)) 147 { 148 character = char.ToLower(character); 149 150 var index = GetArgumentIndex(character); 151 152 if (index < multiInputs.Count) 153 { 154 var input = multiInputs[index]; 155 156 if (cacheArguments && !flow.IsLocal(input)) 157 { 158 flow.SetValue(input, flow.GetValue<object>(input)); 159 } 160 161 return flow.GetValue<object>(input); 162 } 163 } 164 } 165 else 166 { 167 if (Variables.Graph(flow.stack).IsDefined(name)) 168 { 169 return Variables.Graph(flow.stack).Get(name); 170 } 171 172 var self = flow.stack.self; 173 174 if (self != null) 175 { 176 if (Variables.Object(self).IsDefined(name)) 177 { 178 return Variables.Object(self).Get(name); 179 } 180 } 181 182 var scene = flow.stack.scene; 183 184 if (scene != null) 185 { 186 if (Variables.Scene(scene).IsDefined(name)) 187 { 188 return Variables.Scene(scene).Get(name); 189 } 190 } 191 192 if (Variables.Application.IsDefined(name)) 193 { 194 return Variables.Application.Get(name); 195 } 196 197 if (Variables.Saved.IsDefined(name)) 198 { 199 return Variables.Saved.Get(name); 200 } 201 } 202 203 throw new InvalidOperationException($"Unknown expression tree parameter: '{name}'.\nSupported parameter names are alphabetical indices and variable names."); 204 } 205 206 private void EvaluateTreeParameter(Flow flow, string name, ParameterArgs args) 207 { 208 // [param.fieldOrProperty] 209 // [param.parmeterLessMethod()] 210 if (name.Contains(".")) 211 { 212 var parts = name.Split('.'); 213 214 if (parts.Length == 2) 215 { 216 var parameterName = parts[0]; 217 218 var memberName = parts[1].TrimEnd("()"); 219 220 var variableValue = GetParameterValue(flow, parameterName); 221 222 var manipulator = new Member(variableValue.GetType(), memberName, Type.EmptyTypes); 223 224 var target = variableValue; 225 226 if (manipulator.isInvocable) 227 { 228 args.Result = manipulator.Invoke(target); 229 } 230 else if (manipulator.isGettable) 231 { 232 args.Result = manipulator.Get(target); 233 } 234 else 235 { 236 throw new InvalidOperationException($"Cannot get or invoke expression tree parameter: [{parameterName}.{memberName}]"); 237 } 238 } 239 else 240 { 241 throw new InvalidOperationException($"Cannot parse expression tree parameter: [{name}]"); 242 } 243 } 244 else 245 { 246 args.Result = GetParameterValue(flow, name); 247 } 248 } 249 250 public static string GetArgumentName(int index) 251 { 252 if (index > ('z' - 'a')) 253 { 254 throw new NotImplementedException("Argument indices above 26 are not yet supported."); 255 } 256 257 return ((char)('a' + index)).ToString(); 258 } 259 260 public static int GetArgumentIndex(char name) 261 { 262 if (name < 'a' || name > 'z') 263 { 264 throw new NotImplementedException("Unalphabetical argument names are not yet supported."); 265 } 266 267 return name - 'a'; 268 } 269 } 270}