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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFESystem.Browser;

// Cold and warm full editor initialization, including real view text and paint.
// Editing checks are outside the timed interval. No pre-warming or host browser.
internal static class MonacoProfile
{
    internal static void Run(string root, string label, bool profileRegex = false)
    {
        string output = Path.Combine(root, "build/MonacoInit", label + "-" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
        Directory.CreateDirectory(output);
        var inputs = new[] { "Source/XFESystem.Browser", "Source/XFESystem.Collections", "Tools/JintCleanupPatch", "assets/Browser/WebsiteCode", "assets/Browser/Monaco/0.52.2/Core" }
            .SelectMany(directory => Directory.EnumerateFiles(Path.Combine(root, directory)))
            .Where(path => Path.GetExtension(path) is ".cs" or ".js" or ".css" or ".json")
            .ToDictionary(path => Path.GetRelativePath(root, path).Replace('\\', '/'), Hash);
        const string shortSource = "using System;\nnamespace XFE;\npublic class Test { int value = 42; }";
        string longSource = "using System;\nclass Example {\n" + string.Concat(Enumerable.Range(0, 256).Select(i => $"    public int Value{i} => 42;\n")) + "}";
        var cases = new List<object>();
        bool passed = true;
        foreach (var (kind, csharp) in new[] { ("ShortCSharp", shortSource), ("LongCSharp", longSource) })
        for (int attempt = 1; attempt <= 5; attempt++)
        {
            using var canvas = new WindowsTextCanvas(Path.Combine(root, "assets/fonts/cascadia-mono-2407.24/CascadiaMono-Regular.ttf"), 796, 404);
            var timer = Stopwatch.StartNew();
            var trace = new List<object>();
            var regex = profileRegex ? new MonacoRegexProfile() : null;
            BrowserService browser = null!;
            bool installed = false;
            browser = new BrowserService { Trace = line =>
            {
                trace.Add(new { Ticks = timer.ElapsedTicks, Line = line.TrimEnd() });
                if (regex is not null && !installed && line.StartsWith("XFE_BROWSER_SCRIPT_BEGIN", StringComparison.Ordinal))
                { installed = true; regex.Install(browser); }
            } };
            browser.SetCanvas(canvas); browser.ResizeViewport(796, 404);
            long allocated = GC.GetTotalAllocatedBytes();
            int[] collections = Enumerable.Range(0, 3).Select(GC.CollectionCount).ToArray();
            timer.Restart();
            browser.NavigateEditor(csharp, "Test.cs");
            double navigationMs = timer.Elapsed.TotalMilliseconds;
            for (int frame = 1; frame <= 8; frame++) browser.AdvanceTime(frame * 20);
            string proof = browser.ExecuteJavaScript("xfeMonacoEditor.render(true);JSON.stringify({text: Array.from(document.querySelectorAll('.view-lines .view-line')).map(e=>e.textContent.replace(/\\u00a0/g,' ')),language:xfeMonacoEditor.getModel().getLanguageId(),version:xfeMonacoEditor.getModel().getVersionId(),value:xfeMonacoEditor.getValue()})");
            browser.Paint(canvas, new(0, 0, 796, 404), 0); browser.FramePresented();
            double readyMs = timer.Elapsed.TotalMilliseconds;
            long bytes = GC.GetTotalAllocatedBytes() - allocated;
            var dom = typeof(BrowserService).GetField("dom", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(browser)!;
            long Counter(object instance, string name) => (long?)instance.GetType().GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)?.GetValue(instance) ?? 0;
            var workCounters = new { browser.LayoutPassCount, LayoutMilliseconds = Counter(browser, "LayoutTicks") * 1000.0 / Stopwatch.Frequency,
                BudgetValidations = Counter(dom, "BudgetValidationCount"), BudgetNodeVisits = Counter(dom, "BudgetNodeVisits"),
                BudgetMilliseconds = Counter(dom, "BudgetValidationTicks") * 1000.0 / Stopwatch.Frequency,
                Collections = Enumerable.Range(0, 3).Select(i => GC.CollectionCount(i) - collections[i]).ToArray() };
            using var parsed = JsonDocument.Parse(proof);
            var data = parsed.RootElement;
            string[] visible = data.GetProperty("text").EnumerateArray().Select(e => e.GetString()!.TrimEnd()).ToArray();
            bool ready = data.GetProperty("value").GetString() == csharp && data.GetProperty("language").GetString() == "csharp" &&
                visible.Length >= 3 && visible.SequenceEqual(csharp.Split('\n').Take(visible.Length));
            string renderHash = HashText(browser.ExecuteJavaScript("JSON.stringify(Array.from(document.querySelectorAll('.view-lines .view-line')).map(e=>e.innerHTML))"));
            string tokenWork = browser.ExecuteJavaScript("(function(){const model=xfeMonacoEditor.getModel(),value=model.getValue(),original=model.getValue;let reads=0;model.getValue=function(){reads++;return original.apply(this,arguments)};try{const lines=monaco.editor.tokenize(value,'csharp');return JSON.stringify({Lines:lines.length,FullModelReads:reads})}finally{model.getValue=original}})()");
            browser.ExecuteJavaScript("xfeMonacoEditor.focus();xfeMonacoEditor.setPosition({lineNumber:1,column:1});'ready'");
            browser.Key(new("keydown", "x", "KeyX", "x")); browser.Key(new("keyup", "x", "KeyX"));
            bool edited = browser.ReadEditorText() == "x" + csharp;
            canvas.SavePng(Path.Combine(output, kind + "-" + attempt + ".png"));
            using var work = JsonDocument.Parse(tokenWork);
            var row = new { Kind = kind, Attempt = attempt, Cold = kind == "ShortCSharp" && attempt == 1, Passed = ready && edited, NavigationMilliseconds = navigationMs,
                FullReadyMilliseconds = readyMs, AllocatedBytes = bytes, browser.PackagedScriptCacheHits, browser.PackagedScriptPreparations,
                RenderSha256 = renderHash, Proof = data.Clone(), TokenizationWork = work.RootElement.Clone(), Work = workCounters, Regex = regex?.Results(), Trace = trace };
            passed &= row.Passed;
            cases.Add(row);
            Console.WriteLine(JsonSerializer.Serialize(new { row.Kind, row.Attempt, row.Passed, navigationMs, readyMs, bytes, renderHash, row.TokenizationWork, row.Work }));
            browser.Release();
        }
        bool unchanged = inputs.All(pair => Hash(Path.Combine(root, pair.Key)) == pair.Value);
        string result = Path.Combine(output, "Result.json");
        File.WriteAllText(result, JsonSerializer.Serialize(new { Kind = "MonacoFullInitializationHostProfile", Passed = passed && unchanged,
            HostOnly = true, NativeAcceptance = false, Frequency = Stopwatch.Frequency, Sources = inputs,
            ProbeSha256 = Hash(ProbeSource()),
            RuntimeJintSha256 = Hash(typeof(Jint.Engine).Assembly.Location),
            RuntimePatches = new { FailedRegexSuffix = typeof(Jint.Engine).Assembly.GetType("XFESystem.JintFailedRegexSuffixV1") is not null },
            Cases = cases }, new JsonSerializerOptions { WriteIndented = true }));
        Console.WriteLine(result);
        if (!passed || !unchanged) throw new IOException("The full Monaco initialization profile failed.");
    }
    private static string Hash(string path) => Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path)));
    private static string ProbeSource([System.Runtime.CompilerServices.CallerFilePath] string source = "") => source;
    private static string HashText(string value) => Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(value)));
}