From 7b6d84a21f4347a0a19eb5bbcf414b66c0e0846d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20R=C3=A4tzel?= Date: Sun, 7 Jun 2020 17:05:43 +0000 Subject: [PATCH] Support app codes optimizing for runtime expenses I saw apps spending a lot of time on encoding the `Bytes.Bytes` value to base64 when building HTTP responses. As a quick way to optimize runtimes expenses, offer a base64 string directly so that apps can avoid the roundtrip to and from `Bytes.Bytes`. This should become obsolete with a better engine running the Elm code: These values could be cached, but the current engine does not do that. --- .../PersistentProcess.Common/CompileElm.cs | 11 ++++- .../PersistentProcess.Common/ElmApp.cs | 43 +++++++++++++------ .../StartupPublicApp.cs | 22 +++++++--- 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/implement/PersistentProcess/PersistentProcess.Common/CompileElm.cs b/implement/PersistentProcess/PersistentProcess.Common/CompileElm.cs index 7bc5b151..d478ce91 100644 --- a/implement/PersistentProcess/PersistentProcess.Common/CompileElm.cs +++ b/implement/PersistentProcess/PersistentProcess.Common/CompileElm.cs @@ -209,7 +209,16 @@ static public string WithImportsAdded( var firstImportMatch = Regex.Match(originalElmModuleText, @"^import\s", RegexOptions.Multiline); - return originalElmModuleText.Insert(firstImportMatch.Index, importsText + "\n"); + var moduleSyntaxMatch = Regex.Match(originalElmModuleText, @"^module\s[\d\w\.\s\(\)_]+$", RegexOptions.Multiline); + + var insertLocation = + firstImportMatch.Success + ? + firstImportMatch.Index + : + (moduleSyntaxMatch.Index + moduleSyntaxMatch.Length); + + return originalElmModuleText.Insert(insertLocation, "\n" + importsText + "\n"); } static public string WithFunctionAdded( diff --git a/implement/PersistentProcess/PersistentProcess.Common/ElmApp.cs b/implement/PersistentProcess/PersistentProcess.Common/ElmApp.cs index 892f7b4d..c8c62b8a 100644 --- a/implement/PersistentProcess/PersistentProcess.Common/ElmApp.cs +++ b/implement/PersistentProcess/PersistentProcess.Common/ElmApp.cs @@ -156,26 +156,45 @@ byte[] BuildFrontendWebHtml(string elmMakeCommandAppendix) return Encoding.UTF8.GetBytes(frontendWebHtml); } - var elmMakeCommandFromFunctionName = - ImmutableDictionary.Create() - .SetItem("elm_make_frontendWeb_html", null) - .SetItem("elm_make_frontendWeb_html_debug", "--debug"); + string fileExpression(string elmMakeCommandAppendix, bool encodingBase64) + { + var htmlFile = BuildFrontendWebHtml(elmMakeCommandAppendix: elmMakeCommandAppendix); + + var fileAsBase64 = Convert.ToBase64String(htmlFile); + + var base64Expression = "\"" + fileAsBase64 + "\""; + + if (encodingBase64) + return base64Expression; + + return + base64Expression + + @"|> Base64.toBytes |> Maybe.withDefault (""Failed to convert from base64"" |> Bytes.Encode.string |> Bytes.Encode.encode)"; + } + + /* + 2020-06-07 + I saw apps spending a lot of time on encoding the `Bytes.Bytes` value to base64 when building HTTP responses. + As a quick way to optimize runtimes expenses, offer a base64 string directly so that apps can avoid the roundtrip to and from `Bytes.Bytes`. + This should become obsolete with a better engine running the Elm code: These values could be cached, but the current engine does not do that. + */ + + var fileExpressionFromFunctionName = + ImmutableDictionary.Create>() + .SetItem("elm_make_frontendWeb_html", () => fileExpression(elmMakeCommandAppendix: null, encodingBase64: false)) + .SetItem("elm_make_frontendWeb_html_debug", () => fileExpression(elmMakeCommandAppendix: "--debug", encodingBase64: false)) + .SetItem("elm_make_frontendWeb_html_base64", () => fileExpression(elmMakeCommandAppendix: null, encodingBase64: true)) + .SetItem("elm_make_frontendWeb_html_debug_base64", () => fileExpression(elmMakeCommandAppendix: "--debug", encodingBase64: true)); IImmutableDictionary, IImmutableList> replaceFunction( IImmutableDictionary, IImmutableList> previousAppFiles, string functionName, string originalFunctionText) { - if (!elmMakeCommandFromFunctionName.TryGetValue(functionName, out var elmMakeCommandAppendix)) + if (!fileExpressionFromFunctionName.TryGetValue(functionName, out var getFileExpression)) return previousAppFiles; - var htmlFile = BuildFrontendWebHtml(elmMakeCommandAppendix: elmMakeCommandAppendix); - - var fileAsBase64 = Convert.ToBase64String(htmlFile); - - var fileExpression = "\"" + fileAsBase64 + @"""|> Base64.toBytes |> Maybe.withDefault (""Failed to convert from base64"" |> Bytes.Encode.string |> Bytes.Encode.encode)"; - - var newFunctionBody = CompileElmValueSerializer.IndentElmCodeLines(1, fileExpression); + var newFunctionBody = CompileElmValueSerializer.IndentElmCodeLines(1, getFileExpression()); var originalFunctionTextLines = originalFunctionText.Replace("\r", "").Split("\n"); diff --git a/implement/PersistentProcess/PersistentProcess.WebHost/StartupPublicApp.cs b/implement/PersistentProcess/PersistentProcess.WebHost/StartupPublicApp.cs index 092f0b75..40b302b5 100644 --- a/implement/PersistentProcess/PersistentProcess.WebHost/StartupPublicApp.cs +++ b/implement/PersistentProcess/PersistentProcess.WebHost/StartupPublicApp.cs @@ -275,12 +275,22 @@ void processEventAndResultingRequests(InterfaceToHost.Event interfaceEvent) if (headerContentType != null) context.Response.ContentType = headerContentType; - var contentAsByteArray = - httpResponse?.bodyAsBase64 == null - ? - null - : - Convert.FromBase64String(httpResponse.bodyAsBase64); + byte[] contentAsByteArray = null; + + if (httpResponse?.bodyAsBase64 != null) + { + var buffer = new byte[httpResponse.bodyAsBase64.Length * 3 / 4]; + + if (!Convert.TryFromBase64String(httpResponse.bodyAsBase64, buffer, out var bytesWritten)) + { + throw new FormatException( + "Failed to convert from base64. bytesWritten=" + bytesWritten + + ", input.length=" + httpResponse.bodyAsBase64.Length + ", input:\n" + + httpResponse.bodyAsBase64); + } + + contentAsByteArray = buffer.AsSpan(0, bytesWritten).ToArray(); + } context.Response.ContentLength = contentAsByteArray?.Length ?? 0;