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)
        => GetOrPrepare(source, key, out reused, out _, out _);

    internal Prepared<Script> GetOrPrepare(string source, string key, out bool reused, out long preparationTicks, out long admissionTicks)
    {
        reused = false;
        preparationTicks = admissionTicks = 0;
        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. Jint performs function analysis
        // only when needed, as in its ordinary Evaluate(source) path. Count nodes
        // as the parser creates them, rather than traversing the whole AST again.
        long started = System.Diagnostics.Stopwatch.GetTimestamp();
        var prepared = PackagedScriptParser.Parse(source, key, maximumNodes, out int count);
        preparationTicks = System.Diagnostics.Stopwatch.GetTimestamp() - started;
        if (source.Length > maximumCharacters || key.Length > 2_048 || maximumEntries <= 0) return prepared;
        if (count > maximumNodes) return prepared;

        started = System.Diagnostics.Stopwatch.GetTimestamp();
        Monitor.Enter(gate);
        found = Find(key, source);
        if (found is not null)
        {
            reused = true;
            Monitor.Exit(gate);
            admissionTicks = System.Diagnostics.Stopwatch.GetTimestamp() - started;
            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);
        admissionTicks = System.Diagnostics.Stopwatch.GetTimestamp() - started;
        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;
    }
}