XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using Acornima.Ast;
using Jint;

namespace XFESystem.Browser;

/// <summary>
/// Bounded LRU for immutable packaged script syntax. Jint's Prepared instance is
/// documented as reusable across engines; no Engine, realm, JS object, global or
/// evaluated result is retained here. Ordinary network/user scripts never enter.
/// Exact source equality guards changed resources, independent of key collisions.
/// </summary>
internal sealed class PreparedScriptCache(int maximumEntries = 24, int maximumCharacters = 8_388_608, int maximumNodes = 2_000_000)
{
    internal const int MinimumSourceCharacters = 1_024;
    internal static PreparedScriptCache Shared { get; } = new();
    private sealed record Entry(string Key, string Source, Prepared<Script> Script, int Nodes);
    private readonly object gate = new();
    private readonly List<Entry> entries = [];
    private int characters, nodes;

    internal (int Entries, int Characters, int Nodes) Snapshot
    {
        get
        {
            Monitor.Enter(gate);
            var snapshot = (entries.Count, characters, nodes);
            Monitor.Exit(gate);
            return snapshot;
        }
    }

    internal Prepared<Script> GetOrPrepare(string source, string key, out bool reused)
    {
        reused = false;
        Monitor.Enter(gate);
        var found = Find(key, source);
        if (found is not null)
        {
            reused = true;
            Monitor.Exit(gate);
            return found.Script;
        }
        Monitor.Exit(gate);

        // Parse outside the global cache gate. There is no engine/realm capture.
        // Disabling constant folding avoids retaining prepared JS literal values;
        // only syntax/static analysis is reused. Execution constraints are applied
        // by Engine.Evaluate(prepared) exactly as for Engine.Evaluate(source).
        var prepared = Engine.PrepareScript(source, key, strict: false,
            options: new ScriptPreparationOptions { FoldConstants = false });
        if (source.Length > maximumCharacters || key.Length > 2_048 || maximumEntries <= 0) return prepared;
        int count = CountNodes(prepared.Program!, maximumNodes);
        if (count > maximumNodes) return prepared;

        Monitor.Enter(gate);
        found = Find(key, source);
        if (found is not null)
        {
            reused = true;
            Monitor.Exit(gate);
            return found.Script;
        }
        while (entries.Count >= maximumEntries || characters > maximumCharacters - source.Length || nodes > maximumNodes - count)
            RemoveAt(0);
        entries.Add(new(key, source, prepared, count));
        characters += source.Length; nodes += count;
        Monitor.Exit(gate);
        return prepared;
    }

    private Entry? Find(string key, string source)
    {
        for (int index = 0; index < entries.Count; index++)
        {
            var entry = entries[index];
            if (!string.Equals(entry.Key, key, StringComparison.Ordinal)) continue;
            if (!string.Equals(entry.Source, source, StringComparison.Ordinal)) { RemoveAt(index); return null; }
            entries.RemoveAt(index); entries.Add(entry);
            return entry;
        }
        return null;
    }
    private void RemoveAt(int index)
    {
        var entry = entries[index]; entries.RemoveAt(index);
        characters -= entry.Source.Length; nodes -= entry.Nodes;
    }
    private static int CountNodes(Node root, int maximum)
    {
        var pending = new Stack<Node>(); pending.Push(root); int count = 0;
        while (pending.TryPop(out var node))
        {
            if (++count > maximum) return count;
            foreach (var child in node.ChildNodes) pending.Push(child);
        }
        return count;
    }
}