diff --git a/DistantWorlds2.ModLoader.Core/GameDataDefinitionPatching.cs b/DistantWorlds2.ModLoader.Core/GameDataDefinitionPatching.cs index 5d2acf4..662004e 100644 --- a/DistantWorlds2.ModLoader.Core/GameDataDefinitionPatching.cs +++ b/DistantWorlds2.ModLoader.Core/GameDataDefinitionPatching.cs @@ -508,8 +508,18 @@ public static void PatchDynamicDefinition(Type type, object def, YamlSequenceNod if (!pass) continue; - ProcessObjectUpdate(type, def, item, - (_, expr) => Dsl.Parse(expr).Compile(true)); + ConcurrentDictionary> compileCache = new(); + + try { + ProcessObjectUpdate(type, def, item, + (key, expr) => compileCache.GetOrAdd((key,expr), _ => Dsl.Parse(expr).Compile(true))); + } + catch (Exception ex) + { + ModLoader.ModManager.OnUnhandledException(ExceptionDispatchInfo.Capture(ex)); + Console.Error.WriteLine($"Failed to parse {type.Name} @ {item.Start}"); + break; + } Console.WriteLine($"Updated {type.Name} where {whereStr}"); @@ -735,6 +745,8 @@ int ConvertToInt(object id) var def = PrepopulateTyped(Activator.CreateInstance()); + if (def is null) throw new NotImplementedException(); + if (idLookupReq.Value is YamlScalarNode idLookupVarNode) { var idLookupVar = idLookupVarNode.Value; @@ -748,10 +760,11 @@ int ConvertToInt(object id) Dsl["collection"] = null; Dsl["def"] = def; - try - { + ConcurrentDictionary> compileCache = new(); + + try { ProcessObjectUpdate(type, def, item, - (_, expr) => Dsl.Parse(expr).Compile(true)); + (key, expr) => compileCache.GetOrAdd((key,expr), _ => Dsl.Parse(expr).Compile(true))); } catch (Exception ex) { @@ -759,6 +772,7 @@ int ConvertToInt(object id) Console.Error.WriteLine($"Failed to parse {type.Name} @ {item.Start}"); break; } + defs.Add(def); break; } @@ -827,8 +841,18 @@ int ConvertToInt(object id) Dsl["collection"] = null; Dsl["def"] = def; - ProcessObjectUpdate(type, def, item, - (_, expr) => Dsl.Parse(expr).Compile(true)); + ConcurrentDictionary> compileCache = new(); + + try { + ProcessObjectUpdate(type, def, item, + (key, expr) => compileCache.GetOrAdd((key,expr), _ => Dsl.Parse(expr).Compile(true))); + } + catch (Exception ex) + { + ModLoader.ModManager.OnUnhandledException(ExceptionDispatchInfo.Capture(ex)); + Console.Error.WriteLine($"Failed to parse {type.Name} @ {item.Start}"); + break; + } Console.WriteLine($"Updated {type.Name} {id}"); break; @@ -859,6 +883,14 @@ int ConvertToInt(object id) break; } + + if (!defs.Any()) { + Console.Error.WriteLine($"There don't appear to be any {type.Name} @ {whereNode.Start}"); + break; + } + + Func? whereFn = null; + foreach (var def in defs) { var idObj = GetId(def); @@ -869,11 +901,10 @@ int ConvertToInt(object id) Dsl["collection"] = null; Dsl["value"] = idVal; Dsl["def"] = def; - - Func whereFn; + try { - whereFn = Dsl.Parse(whereStr).Compile(true); + whereFn ??= Dsl.Parse(whereStr).Compile(true); } catch { @@ -896,8 +927,18 @@ int ConvertToInt(object id) if (!pass) continue; - ProcessObjectUpdate(type, def, item, - (_, expr) => Dsl.Parse(expr).Compile(true)); + ConcurrentDictionary> compileCache = new(); + + try { + ProcessObjectUpdate(type, def, item, + (key, expr) => compileCache.GetOrAdd((key,expr), _ => Dsl.Parse(expr).Compile(true))); + } + catch (Exception ex) + { + ModLoader.ModManager.OnUnhandledException(ExceptionDispatchInfo.Capture(ex)); + Console.Error.WriteLine($"Failed to parse {type.Name} @ {item.Start}"); + break; + } Console.WriteLine($"Updated {type.Name} {idVal}"); } @@ -1193,6 +1234,8 @@ int ConvertToInt(object id) var def = PrepopulateTyped(Activator.CreateInstance()); + if (def is null) throw new NotImplementedException(); + if (idLookupReq.Value is YamlScalarNode idLookupVarNode) { var idLookupVar = idLookupVarNode.Value; @@ -1206,10 +1249,12 @@ int ConvertToInt(object id) Dsl["collection"] = null; Dsl["def"] = def; + ConcurrentDictionary> compileCache = new(); + try { ProcessObjectUpdate(type, def, item, - (_, expr) => Dsl.Parse(expr).Compile(true)); + (key, expr) => compileCache.GetOrAdd((key,expr), k => Dsl.Parse(expr).Compile(true))); } catch (Exception ex) { @@ -1298,8 +1343,18 @@ int ConvertToInt(object id) Dsl["collection"] = null; Dsl["def"] = def; - ProcessObjectUpdate(type, def, item, - (_, expr) => Dsl.Parse(expr).Compile(true)); + ConcurrentDictionary> compileCache = new(); + + try { + ProcessObjectUpdate(type, def, item, + (key, expr) => compileCache.GetOrAdd((key,expr), _ => Dsl.Parse(expr).Compile(true))); + } + catch (Exception ex) + { + ModLoader.ModManager.OnUnhandledException(ExceptionDispatchInfo.Capture(ex)); + Console.Error.WriteLine($"Failed to parse {type.Name} @ {item.Start}"); + break; + } Console.WriteLine($"Updated {type.Name} {id}"); @@ -1326,18 +1381,28 @@ int ConvertToInt(object id) Console.Error.WriteLine($"Can't parse update-all where clause @ {whereNode.Start}"); break; } + + if (!defs.Any()) { + Console.Error.WriteLine($"There don't appear to be any {type.Name} @ {whereNode.Start}"); + break; + } + + Func? whereFn = null; foreach (var def in defs) { + var idObj = GetId(def); + + var idVal = ((IConvertible)idObj).ToDouble(NumberFormatInfo.InvariantInfo); + Dsl["item"] = null; - Dsl["value"] = null; Dsl["collection"] = null; + Dsl["value"] = idVal; Dsl["def"] = def; - - Func whereFn; + try { - whereFn = Dsl.Parse(whereStr).Compile(true); + whereFn ??= Dsl.Parse(whereStr).Compile(true); } catch { @@ -1357,15 +1422,21 @@ int ConvertToInt(object id) break; } - var idObj = GetId(def); - - var idVal = ((IConvertible)idObj).ToDouble(NumberFormatInfo.InvariantInfo); - if (!pass) continue; - ProcessObjectUpdate(type, def, item, - (_, expr) => Dsl.Parse(expr).Compile(true)); + ConcurrentDictionary> compileCache = new(); + + try { + ProcessObjectUpdate(type, def, item, + (key, expr) => compileCache.GetOrAdd((key,expr), _ => Dsl.Parse(expr).Compile(true))); + } + catch (Exception ex) + { + ModLoader.ModManager.OnUnhandledException(ExceptionDispatchInfo.Capture(ex)); + Console.Error.WriteLine($"Failed to parse {type.Name} @ {item.Start}"); + break; + } Console.WriteLine($"Updated {type.Name} {idVal}"); } @@ -1695,7 +1766,6 @@ private static void ParseCollectionUpdate(Type collectionType, ref IList collect if (keyStr[0] == '(' && keyStr[keyStr.Length - 1] == ')') { - // no state, always cache try { for (var i = 0; i < collection.Count; i++) @@ -1707,7 +1777,7 @@ private static void ParseCollectionUpdate(Type collectionType, ref IList collect object result; try { - result = compileFn("", keyStr.Substring(1, keyStr.Length - 2))(); + result = compileFn(collection, keyStr.Substring(1, keyStr.Length - 2))(); } catch { @@ -1762,7 +1832,7 @@ or TypeCode.Decimal IConvertible newValue = valStr; try { Dsl["value"] = initValue; - var fn = compileFn("", valStr); + var fn = compileFn(collection, valStr); newValue = (IConvertible)fn(); } catch (Exception ex) { diff --git a/DistantWorlds2.ModLoader.Core/MmVariableDslBase.cs b/DistantWorlds2.ModLoader.Core/MmVariableDslBase.cs index 667ec90..5d68f23 100644 --- a/DistantWorlds2.ModLoader.Core/MmVariableDslBase.cs +++ b/DistantWorlds2.ModLoader.Core/MmVariableDslBase.cs @@ -17,14 +17,13 @@ public object? this[string symbol] private static ConcurrentDictionary StaticVariableSource => ModLoader.ModManager.SharedVariables; - - public override Expression? ResolveGlobalSymbol(string symbol) + public override object? ResolveGlobalSymbol(string symbol) { - var expr = base.ResolveGlobalSymbol(symbol); - return expr ?? ( - StaticVariableSource.TryGetValue(symbol, out var obj) - ? Expression.Constant(obj, obj.GetType()) - : expr + var obj = base.ResolveGlobalSymbol(symbol); + return obj ?? ( + StaticVariableSource.TryGetValue(symbol, out obj) + ? obj + : null ); } } diff --git a/DistantWorlds2.ModLoader.Dsl/DslBase.cs b/DistantWorlds2.ModLoader.Dsl/DslBase.cs index 2c130f5..2e89e8c 100644 --- a/DistantWorlds2.ModLoader.Dsl/DslBase.cs +++ b/DistantWorlds2.ModLoader.Dsl/DslBase.cs @@ -90,6 +90,8 @@ public abstract class DslBase { private static readonly MethodInfo MiFrac = ReflectionUtils.Method(() => Frac(0d)); private static readonly MethodInfo MiContains = ReflectionUtils.Method(() => Contains(null!, null!)); + + private static readonly MethodInfo MiResolveGlobalSymbol = ReflectionUtils.Method(x => x.ResolveGlobalSymbol("")); // ReSharper restore ReturnValueOfPureMethodIsNotUsed private static readonly ConcurrentDictionary RegexCache = new(); @@ -724,7 +726,8 @@ protected virtual IEnumerable PropertyDefinitions() { SymbolPathExpressionBuilder); } - private Expression SymbolPathExpressionBuilder(string text) { + private Expression SymbolPathExpressionBuilder(string text) + { // first evaluate brackets var bracketMatches = RxBrackets.Matches(text); var bracketMatchCount = bracketMatches.Count; @@ -732,43 +735,57 @@ private Expression SymbolPathExpressionBuilder(string text) { return SymbolPathExpressionBuilderSansBrackets(text); var bracketSubExps = new Queue(bracketMatchCount); - for (var i = 0; i < bracketMatchCount; ++i) { + for (var i = 0; i < bracketMatchCount; ++i) + { var m = bracketMatches[i]; bracketSubExps.Enqueue(Parse(text.Substring(m.Index, m.Length)).Body); } var parts = text.Split('.'); var leftSym = parts.First(); - var expr = ResolveGlobalSymbol(leftSym, bracketSubExps); - if (expr is null) - throw new InvalidOperationException($"{leftSym} not found."); + var expr = ResolveGlobalExpression(leftSym, bracketSubExps); - foreach (var sym in parts.Skip(1)) { + foreach (var sym in parts.Skip(1)) + { var bracketIndex = sym.IndexOf('['); - if (bracketIndex != -1) { - var subSym = sym.Substring(0, bracketIndex); - expr = ResolveSubscript(bracketSubExps, Expression.PropertyOrField(expr, subSym)); - continue; - } - - expr = Expression.PropertyOrField(expr, sym); + if (bracketIndex == -1) + expr = Expression.PropertyOrField(expr, sym); + else + expr = ResolveSubscript(Expression.PropertyOrField(expr, + sym.Substring(0, bracketIndex)), + bracketSubExps.Dequeue()); } - return expr; } - private Expression SymbolPathExpressionBuilderSansBrackets(string text) { + private Expression ResolveGlobalExpression(string symbol) + { + var obj = ResolveGlobalSymbol(symbol); + if (obj is null) + throw new InvalidOperationException($"{symbol} not found."); + + var t = obj.GetType(); + + return Expression.Convert( + Expression.Call( + Expression.Constant(this), + MiResolveGlobalSymbol, + Expression.Constant(symbol)), + t); + } + + private Expression SymbolPathExpressionBuilderSansBrackets(string text) + { var parts = text.Split('.'); var leftSym = parts.First(); - var expr = ResolveGlobalSymbol(leftSym); - if (expr is null) - throw new InvalidOperationException($"{leftSym} not found."); + + var expr = ResolveGlobalExpression(leftSym); foreach (var sym in parts.Skip(1)) expr = Expression.PropertyOrField(expr, sym); + return expr; } - /// /// Returns the definitions for whitespace used within the language. /// @@ -777,76 +794,66 @@ protected virtual IEnumerable WhitespaceDefinitions() { yield return new GrammarDefinition("SPACE", @"\s+", true); } - public ConcurrentDictionary Globals = new(); + public ConcurrentDictionary Globals = new(); - public ConcurrentDictionary Variables = new(); + public ConcurrentDictionary Variables = new(); - public DslBase() { - _language = new(() => new(AllDefinitions().ToArray())); - } - - public virtual Expression? ResolveGlobalSymbol(string symbol, Queue subExps) { - var bracketIndex = symbol.IndexOf('['); - if (bracketIndex == -1) - return ResolveGlobalSymbol(symbol); - - symbol = symbol.Substring(0, bracketIndex); - var expr = ResolveGlobalSymbol(symbol); - return expr == null ? null : ResolveSubscript(subExps, expr); - } - - private static Expression ResolveSubscript(Queue subExps, Expression expr) { - if (subExps is null) throw new ArgumentNullException(nameof(subExps)); - if (expr is null) throw new ArgumentNullException(nameof(expr)); - var exprType = expr.Type; - if (exprType.IsArray) - return Expression.ArrayAccess(expr, subExps.Dequeue()); + public virtual Expression ResolveGlobalExpression(string symbol, Queue subExps) + { + var bracketIndex = symbol.IndexOf('['); + if (bracketIndex == -1) + return ResolveGlobalExpression(symbol); - var indexer = ReflectionUtils.Indexer(exprType); - if (indexer is not null) - return Expression.MakeIndex(expr, indexer, new[] { subExps.Dequeue() }); + symbol = symbol.Substring(0, bracketIndex); + var expr = ResolveGlobalExpression(symbol); + return ResolveSubscript(expr, subExps.Dequeue()); - throw new NotImplementedException($"Subscripting {exprType.FullName}"); - } + } + private static Expression ResolveSubscript(Expression expr, Expression subExpr) + { + if (subExpr is null) throw new ArgumentNullException(nameof(subExpr)); + if (expr is null) throw new ArgumentNullException(nameof(expr)); + var t = expr.Type; + if (t.IsArray) + return Expression.ArrayAccess(expr, subExpr); + var indexer = ReflectionUtils.Indexer(t); + if (indexer is not null) + return Expression.MakeIndex(expr, indexer, new[] { subExpr }); + throw new NotImplementedException($"Subscripting {t.FullName}"); + } - public virtual Expression? ResolveGlobalSymbol(string symbol) - => Globals.TryGetValue(symbol, out var expr) - ? expr - : Variables.TryGetValue(symbol, out expr) - ? expr - : null; - - public object? GetGlobal(string symbol) - => Globals.TryGetValue(symbol, out var e) - ? e is ConstantExpression ce - ? ce.Value - : e - : null; - - public void SetGlobal(string symbol, object? value) { - if (value is null) - Globals.TryRemove(symbol, out _); - else - Globals[symbol] = value is Expression e - ? e - : Expression.Constant(value); - } + public virtual object? ResolveGlobalSymbol(string symbol) + => Globals.TryGetValue(symbol, out var value) + ? value + : Variables.TryGetValue(symbol, out value) + ? value + : null; + + public object? GetGlobal(string symbol) + => Globals.TryGetValue(symbol, out var value) + ? value + : null; + + public void SetGlobal(string symbol, object? value) + { + if (value is null) + Globals.TryRemove(symbol, out _); + else + Globals[symbol] = value; + } - public object? GetVariable(string symbol) - => Variables.TryGetValue(symbol, out var e) - ? e is ConstantExpression ce - ? ce.Value - : e - : null; - - public void SetVariable(string symbol, object? value) { - if (value is null) - Variables.TryRemove(symbol, out _); - else - Variables[symbol] = value is Expression e - ? e - : Expression.Constant(value); - } + public object? GetVariable(string symbol) + => Variables.TryGetValue(symbol, out var value) + ? value + : null; + + public void SetVariable(string symbol, object? value) + { + if (value is null) + Variables.TryRemove(symbol, out _); + else + Variables[symbol] = value; + } } \ No newline at end of file