diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Interop/InternalRegisteredFunction.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Interop/InternalRegisteredFunction.ts index fa1a98f69d..fe7a4cd82b 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Interop/InternalRegisteredFunction.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Interop/InternalRegisteredFunction.ts @@ -1,5 +1,5 @@ import { invokeWithJsonMarshalling } from './InvokeWithJsonMarshalling'; -import { attachComponentToElement, renderRenderTree } from '../Rendering/Renderer'; +import { attachComponentToElement, renderBatch } from '../Rendering/Renderer'; /** * The definitive list of internal functions invokable from .NET code. @@ -8,5 +8,5 @@ import { attachComponentToElement, renderRenderTree } from '../Rendering/Rendere export const internalRegisteredFunctions = { attachComponentToElement, invokeWithJsonMarshalling, - renderRenderTree, + renderBatch, }; diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Mono/MonoPlatform.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Mono/MonoPlatform.ts index c710e606c1..6f24cd086f 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Mono/MonoPlatform.ts @@ -124,6 +124,10 @@ export const monoPlatform: Platform = { const fieldValue = Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32'); return fieldValue === 0 ? null : monoPlatform.toJavaScriptString(fieldValue as any as System_String); }, + + readStructField: function readStructField(baseAddress: Pointer, fieldOffset?: number): Pointer { + return ((baseAddress as any as number) + (fieldOffset || 0)) as any as Pointer; + }, }; // Bypass normal type checking to add this extra function. It's only intended to be called from diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Platform.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Platform.ts index 032defcdd9..ddefea35da 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Platform.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Platform.ts @@ -15,6 +15,7 @@ readInt32Field(baseAddress: Pointer, fieldOffset?: number): number; readObjectField(baseAddress: Pointer, fieldOffset?: number): System_Object; readStringField(baseAddress: Pointer, fieldOffset?: number): string | null; + readStructField(baseAddress: Pointer, fieldOffset?: number): Pointer; } // We don't actually instantiate any of these at runtime. For perf it's preferable to diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch.ts new file mode 100644 index 0000000000..8d99186863 --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch.ts @@ -0,0 +1,27 @@ +import { Pointer, System_Array } from '../Platform/Platform'; +import { platform } from '../Environment'; + +// Keep in sync with the structs in .NET code + +export const renderBatch = { + updatedComponents: (obj: RenderBatchPointer) => platform.readStructField(obj, 0) as ArrayRangePointer, +}; + +const arrayRangeStructLength = 8; +export const arrayRange = { + array: (obj: ArrayRangePointer) => platform.readObjectField(obj, 0) as System_Array, + count: (obj: ArrayRangePointer) => platform.readInt32Field(obj, 4), +}; + +export const renderTreeDiffStructLength = 4 + 2 * arrayRangeStructLength; +export const renderTreeDiff = { + componentId: (obj: RenderTreeDiffPointer) => platform.readInt32Field(obj, 0), + edits: (obj: RenderTreeDiffPointer) => platform.readStructField(obj, 4) as ArrayRangePointer, + currentState: (obj: RenderTreeDiffPointer) => platform.readStructField(obj, 4 + arrayRangeStructLength) as ArrayRangePointer, +}; + +// Nominal types to ensure only valid pointers are passed to the functions above. +// At runtime the values are just numbers. +export interface RenderBatchPointer extends Pointer { RenderBatchPointer__DO_NOT_IMPLEMENT: any } +export interface ArrayRangePointer extends Pointer { ArrayRangePointer__DO_NOT_IMPLEMENT: any } +export interface RenderTreeDiffPointer extends Pointer { RenderTreeDiffPointer__DO_NOT_IMPLEMENT: any } diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderComponentArgs.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderComponentArgs.ts deleted file mode 100644 index 7ecbfc6aff..0000000000 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderComponentArgs.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Pointer, System_Array } from '../Platform/Platform'; -import { platform } from '../Environment'; - -// Keep in sync with the RenderComponentArgs struct in .NET code -export const renderComponentArgs = { - browserRendererId: (obj: RenderComponentArgsPointer) => platform.readInt32Field(obj, 0), - componentId: (obj: RenderComponentArgsPointer) => platform.readInt32Field(obj, 4), - renderTreeEdits: (obj: RenderComponentArgsPointer) => platform.readObjectField(obj, 8) as System_Array, - renderTreeEditsLength: (obj: RenderComponentArgsPointer) => platform.readInt32Field(obj, 12), - renderTree: (obj: RenderComponentArgsPointer) => platform.readObjectField(obj, 16) as System_Array -} - -// Nominal type to ensure only valid pointers are passed to the renderComponentArgs functions. -// At runtime the values are just numbers. -export interface RenderComponentArgsPointer extends Pointer { RenderComponentArgsPointer__DO_NOT_IMPLEMENT: any } diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/Renderer.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/Renderer.ts index 081b54292f..740bfdb726 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/Renderer.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/Renderer.ts @@ -2,7 +2,7 @@ import { platform } from '../Environment'; import { getTreeNodePtr, renderTreeNode, NodeType, RenderTreeNodePointer } from './RenderTreeNode'; import { RenderTreeEditPointer } from './RenderTreeEdit'; -import { renderComponentArgs, RenderComponentArgsPointer } from './RenderComponentArgs'; +import { renderBatch as renderBatchStruct, arrayRange, renderTreeDiffStructLength, renderTreeDiff, RenderBatchPointer, RenderTreeDiffPointer } from './RenderBatch'; import { BrowserRenderer } from './BrowserRenderer'; type BrowserRendererRegistry = { [browserRendererId: number]: BrowserRenderer }; @@ -23,19 +23,27 @@ export function attachComponentToElement(browserRendererId: number, elementSelec clearElement(element); } -export function renderRenderTree(args: RenderComponentArgsPointer) { - const browserRendererId = renderComponentArgs.browserRendererId(args); +export function renderBatch(browserRendererId: number, batch: RenderBatchPointer) { const browserRenderer = browserRenderers[browserRendererId]; if (!browserRenderer) { throw new Error(`There is no browser renderer with ID ${browserRendererId}.`); } + + const updatedComponents = renderBatchStruct.updatedComponents(batch); + const updatedComponentsLength = arrayRange.count(updatedComponents); + const updatedComponentsArray = arrayRange.array(updatedComponents); + for (var i = 0; i < updatedComponentsLength; i++) { + const updatedComponentDiff = platform.getArrayEntryPtr(updatedComponentsArray, i, renderTreeDiffStructLength) as RenderTreeDiffPointer; + const componentId = renderTreeDiff.componentId(updatedComponentDiff); - const componentId = renderComponentArgs.componentId(args); - const edits = renderComponentArgs.renderTreeEdits(args); - const editsLength = renderComponentArgs.renderTreeEditsLength(args); - const tree = renderComponentArgs.renderTree(args); + const editsArrayRange = renderTreeDiff.edits(updatedComponentDiff); + const currentStateArrayRange = renderTreeDiff.currentState(updatedComponentDiff); - browserRenderer.updateComponent(componentId, edits, editsLength, tree); + const edits = arrayRange.array(editsArrayRange); + const editsLength = arrayRange.count(editsArrayRange); + const tree = arrayRange.array(currentStateArrayRange); + browserRenderer.updateComponent(componentId, edits, editsLength, tree); + } } function clearElement(element: Element) { diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs index 96b188f653..49d5091672 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs @@ -68,33 +68,10 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering /// protected override void UpdateDisplay(RenderBatch batch) { - // TODO: Pass to JS in a single call - for (var i = 0; i < batch.UpdatedComponents.Count; i++) - { - ref var diff = ref batch.UpdatedComponents.Array[i]; - RegisteredFunction.InvokeUnmarshalled( - "renderRenderTree", - new RenderComponentArgs - { - BrowserRendererId = _browserRendererId, - ComponentId = diff.ComponentId, - RenderTreeEdits = diff.Edits.Array, - RenderTreeEditsLength = diff.Edits.Count, - RenderTree = diff.CurrentState.Array - }); - } - } - - // Encapsulates the data we pass to the JS rendering function - private struct RenderComponentArgs - { - // Important: If you edit this struct, keep it in sync with RenderComponentArgs.ts - - public int BrowserRendererId; - public int ComponentId; - public RenderTreeEdit[] RenderTreeEdits; - public int RenderTreeEditsLength; - public RenderTreeNode[] RenderTree; + RegisteredFunction.InvokeUnmarshalled( + "renderBatch", + _browserRendererId, + batch); } } }