babalae/better-genshin-impact · error · InvalidOperationException

变量 存在循环引用

Error message

变量 {name} 存在循环引用

What it means

ResolveVariable uses a _resolvingVars HashSet to track the in-progress resolution chain. If adding the current name fails, a cycle is detected (var A depends on var B which depends back on var A), and InvalidOperationException is thrown to prevent infinite recursion.

Solutions

  1. Break the cycle by making at least one variable independent (a constant or expression of builtins only).
  2. Re-express one var in terms of cw/ch/cx/cy/s rather than another var.
  3. Run a cycle check over the vars graph during config validation.

Example fix

// before
// "vars": { "a": "b + 1", "b": "a + 1" }  -> cycle -> throws

// after
// "vars": { "a": "b + 1", "b": "cw * 0.1" }
Defensive patterns

Strategy: validation

Validate before calling

static void AssertNoVarCycles(RecognitionObjectJsonFile config)
{
    var deps = config.Vars.ToDictionary(
        kv => kv.Key,
        kv => System.Text.RegularExpressions.Regex.Matches(kv.Value, "[A-Za-z_][A-Za-z0-9_]*")
            .Select(m => m.Value).Where(v => config.Vars.ContainsKey(v)).ToHashSet());
    var seen = new HashSet<string>();
    bool Dfs(string n, HashSet<string> stack)
    {
        if (stack.Contains(n)) return true;
        if (!seen.Add(n)) return false;
        stack.Add(n);
        foreach (var d in deps[n]) if (Dfs(d, stack)) return true;
        stack.Remove(n);
        return false;
    }
    foreach (var n in config.Vars.Keys)
        if (Dfs(n, new())) throw new InvalidOperationException($"Cycle detected in vars involving '{n}'.");
}

Try / catch

try { return RecognitionObjectJsonLoader.Load(config, objectName, context); }
catch (InvalidOperationException ex) when (ex.Message.Contains("循环引用"))
{ Logger.LogError(ex, "Circular var reference in Recognition.json."); throw; }

Prevention

When it happens

Trigger: vars declares mutually referencing variables, e.g. { "a": "b + 1", "b": "a + 1" }, or a self-reference like { "a": "a + 1" }.

Common situations: Two vars accidentally reference each other; a var self-references during a refactor; copy-paste of an expression that points back at itself.

Related errors


AI-assisted analysis of babalae/better-genshin-impact@a7cb36712d (2026-08-13). Data as JSON: /api/errors/5eb516507364a6c2. Report an issue: GitHub.

Appendix: source

Thrown at BetterGenshinImpact/Core/Recognition/RecognitionObjectJsonLoader.cs:415

            return parameters;
        }

        private double ResolveVariable(string name)
        {
            if (_resolvedVars.TryGetValue(name, out var resolvedValue))
            {
                return resolvedValue;
            }

            if (!_config.Vars.TryGetValue(name, out var expression))
            {
                throw new KeyNotFoundException($"未找到变量 {name}");
            }

            if (!_resolvingVars.Add(name))
            {
                throw new InvalidOperationException($"变量 {name} 存在循环引用");
            }

            try
            {
                var value = ToDouble(EvaluateValue(expression, name));
                _resolvedVars[name] = value;
                return value;
            }
            finally
            {
                _resolvingVars.Remove(name);
            }
        }

        private string NormalizeExpression(string expression)
        {
            var normalized = expression;
            var parameterNames = GetParameterNames()

View on GitHub (pinned to a7cb36712d)