babalae/better-genshin-impact · error · InvalidOperationException

变量 {name} 存在循环引用

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.

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)

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.