Add RenderBatch reader abstraction (#1052)

* In .JS project, make RenderBatch into an interface with SharedMemoryRenderBatch as implementation

* In SharedMemoryRenderBatch, use prototype functions instead of assigning references on each instantiation
This commit is contained in:
Steve Sanderson 2018-07-02 10:21:41 +01:00 committed by GitHub
parent 389ff14a82
commit b869ced648
8 changed files with 271 additions and 197 deletions

View File

@ -2,6 +2,8 @@ import { platform } from './Environment';
import { navigateTo, internalFunctions as uriHelperInternalFunctions } from './Services/UriHelper';
import { internalFunctions as httpInternalFunctions } from './Services/Http';
import { attachRootComponentToElement, renderBatch } from './Rendering/Renderer';
import { Pointer } from './Platform/Platform';
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
if (typeof window !== 'undefined') {
// When the library is loaded in a browser via a <script> element, make the
@ -12,7 +14,7 @@ if (typeof window !== 'undefined') {
_internal: {
attachRootComponentToElement,
renderBatch,
renderBatch: (browserRendererId: number, batchAddress: Pointer) => renderBatch(browserRendererId, new SharedMemoryRenderBatch(batchAddress)),
http: httpInternalFunctions,
uriHelper: uriHelperInternalFunctions
}

View File

@ -1,6 +1,5 @@
import { System_Array, MethodHandle } from '../Platform/Platform';
import { getRenderTreeEditPtr, renderTreeEdit, RenderTreeEditPointer, EditType } from './RenderTreeEdit';
import { getTreeFramePtr, renderTreeFrame, FrameType, RenderTreeFramePointer } from './RenderTreeFrame';
import { RenderBatch, ArraySegment, ArrayRange, RenderTreeEdit, RenderTreeFrame, EditType, FrameType, ArrayValues } from './RenderBatch/RenderBatch';
import { platform } from '../Environment';
import { EventDelegator } from './EventDelegator';
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
@ -24,13 +23,13 @@ export class BrowserRenderer {
this.attachComponentToElement(componentId, toLogicalElement(element));
}
public updateComponent(componentId: number, edits: System_Array<RenderTreeEditPointer>, editsOffset: number, editsLength: number, referenceFrames: System_Array<RenderTreeFramePointer>) {
public updateComponent(batch: RenderBatch, componentId: number, edits: ArraySegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>) {
const element = this.childComponentLocations[componentId];
if (!element) {
throw new Error(`No element is currently associated with component ${componentId}`);
}
this.applyEdits(componentId, element, 0, edits, editsOffset, editsLength, referenceFrames);
this.applyEdits(batch, componentId, element, 0, edits, referenceFrames);
}
public disposeComponent(componentId: number) {
@ -45,33 +44,41 @@ export class BrowserRenderer {
this.childComponentLocations[componentId] = element;
}
private applyEdits(componentId: number, parent: LogicalElement, childIndex: number, edits: System_Array<RenderTreeEditPointer>, editsOffset: number, editsLength: number, referenceFrames: System_Array<RenderTreeFramePointer>) {
private applyEdits(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, edits: ArraySegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>) {
let currentDepth = 0;
let childIndexAtCurrentDepth = childIndex;
const arraySegmentReader = batch.arraySegmentReader;
const editReader = batch.editReader;
const frameReader = batch.frameReader;
const editsValues = arraySegmentReader.values(edits);
const editsOffset = arraySegmentReader.offset(edits);
const editsLength = arraySegmentReader.count(edits);
const maxEditIndexExcl = editsOffset + editsLength;
for (let editIndex = editsOffset; editIndex < maxEditIndexExcl; editIndex++) {
const edit = getRenderTreeEditPtr(edits, editIndex);
const editType = renderTreeEdit.type(edit);
const edit = batch.diffReader.editsEntry(editsValues, editIndex);
const editType = editReader.editType(edit);
switch (editType) {
case EditType.prependFrame: {
const frameIndex = renderTreeEdit.newTreeIndex(edit);
const frame = getTreeFramePtr(referenceFrames, frameIndex);
const siblingIndex = renderTreeEdit.siblingIndex(edit);
this.insertFrame(componentId, parent, childIndexAtCurrentDepth + siblingIndex, referenceFrames, frame, frameIndex);
const frameIndex = editReader.newTreeIndex(edit);
const frame = batch.referenceFramesEntry(referenceFrames, frameIndex);
const siblingIndex = editReader.siblingIndex(edit);
this.insertFrame(batch, componentId, parent, childIndexAtCurrentDepth + siblingIndex, referenceFrames, frame, frameIndex);
break;
}
case EditType.removeFrame: {
const siblingIndex = renderTreeEdit.siblingIndex(edit);
const siblingIndex = editReader.siblingIndex(edit);
removeLogicalChild(parent, childIndexAtCurrentDepth + siblingIndex);
break;
}
case EditType.setAttribute: {
const frameIndex = renderTreeEdit.newTreeIndex(edit);
const frame = getTreeFramePtr(referenceFrames, frameIndex);
const siblingIndex = renderTreeEdit.siblingIndex(edit);
const frameIndex = editReader.newTreeIndex(edit);
const frame = batch.referenceFramesEntry(referenceFrames, frameIndex);
const siblingIndex = editReader.siblingIndex(edit);
const element = getLogicalChild(parent, childIndexAtCurrentDepth + siblingIndex);
if (element instanceof HTMLElement) {
this.applyAttribute(componentId, element, frame);
this.applyAttribute(batch, componentId, element, frame);
} else {
throw new Error(`Cannot set attribute on non-element child`);
}
@ -80,12 +87,12 @@ export class BrowserRenderer {
case EditType.removeAttribute: {
// Note that we don't have to dispose the info we track about event handlers here, because the
// disposed event handler IDs are delivered separately (in the 'disposedEventHandlerIds' array)
const siblingIndex = renderTreeEdit.siblingIndex(edit);
const siblingIndex = editReader.siblingIndex(edit);
const element = getLogicalChild(parent, childIndexAtCurrentDepth + siblingIndex);
if (element instanceof HTMLElement) {
const attributeName = renderTreeEdit.removedAttributeName(edit)!;
const attributeName = editReader.removedAttributeName(edit)!;
// First try to remove any special property we use for this attribute
if (!this.tryApplySpecialProperty(element, attributeName, null)) {
if (!this.tryApplySpecialProperty(batch, element, attributeName, null)) {
// If that's not applicable, it's a regular DOM attribute so remove that
element.removeAttribute(attributeName);
}
@ -95,19 +102,19 @@ export class BrowserRenderer {
break;
}
case EditType.updateText: {
const frameIndex = renderTreeEdit.newTreeIndex(edit);
const frame = getTreeFramePtr(referenceFrames, frameIndex);
const siblingIndex = renderTreeEdit.siblingIndex(edit);
const frameIndex = editReader.newTreeIndex(edit);
const frame = batch.referenceFramesEntry(referenceFrames, frameIndex);
const siblingIndex = editReader.siblingIndex(edit);
const textNode = getLogicalChild(parent, childIndexAtCurrentDepth + siblingIndex);
if (textNode instanceof Text) {
textNode.textContent = renderTreeFrame.textContent(frame);
textNode.textContent = frameReader.textContent(frame);
} else {
throw new Error(`Cannot set text content on non-text child`);
}
break;
}
case EditType.stepIn: {
const siblingIndex = renderTreeEdit.siblingIndex(edit);
const siblingIndex = editReader.siblingIndex(edit);
parent = getLogicalChild(parent, childIndexAtCurrentDepth + siblingIndex);
currentDepth++;
childIndexAtCurrentDepth = 0;
@ -127,25 +134,26 @@ export class BrowserRenderer {
}
}
private insertFrame(componentId: number, parent: LogicalElement, childIndex: number, frames: System_Array<RenderTreeFramePointer>, frame: RenderTreeFramePointer, frameIndex: number): number {
const frameType = renderTreeFrame.frameType(frame);
private insertFrame(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, frame: RenderTreeFrame, frameIndex: number): number {
const frameReader = batch.frameReader;
const frameType = frameReader.frameType(frame);
switch (frameType) {
case FrameType.element:
this.insertElement(componentId, parent, childIndex, frames, frame, frameIndex);
this.insertElement(batch, componentId, parent, childIndex, frames, frame, frameIndex);
return 1;
case FrameType.text:
this.insertText(parent, childIndex, frame);
this.insertText(batch, parent, childIndex, frame);
return 1;
case FrameType.attribute:
throw new Error('Attribute frames should only be present as leading children of element frames.');
case FrameType.component:
this.insertComponent(parent, childIndex, frame);
this.insertComponent(batch, parent, childIndex, frame);
return 1;
case FrameType.region:
return this.insertFrameRange(componentId, parent, childIndex, frames, frameIndex + 1, frameIndex + renderTreeFrame.subtreeLength(frame));
return this.insertFrameRange(batch, componentId, parent, childIndex, frames, frameIndex + 1, frameIndex + frameReader.subtreeLength(frame));
case FrameType.elementReferenceCapture:
if (parent instanceof Element) {
applyCaptureIdToElement(parent, renderTreeFrame.elementReferenceCaptureId(frame));
applyCaptureIdToElement(parent, frameReader.elementReferenceCaptureId(frame));
return 0; // A "capture" is a child in the diff, but has no node in the DOM
} else {
throw new Error('Reference capture frames can only be children of element frames.');
@ -156,8 +164,9 @@ export class BrowserRenderer {
}
}
private insertElement(componentId: number, parent: LogicalElement, childIndex: number, frames: System_Array<RenderTreeFramePointer>, frame: RenderTreeFramePointer, frameIndex: number) {
const tagName = renderTreeFrame.elementName(frame)!;
private insertElement(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, frame: RenderTreeFrame, frameIndex: number) {
const frameReader = batch.frameReader;
const tagName = frameReader.elementName(frame)!;
const newDomElementRaw = tagName === 'svg' || isSvgElement(parent) ?
document.createElementNS('http://www.w3.org/2000/svg', tagName) :
document.createElement(tagName);
@ -165,39 +174,40 @@ export class BrowserRenderer {
insertLogicalChild(newDomElementRaw, parent, childIndex);
// Apply attributes
const descendantsEndIndexExcl = frameIndex + renderTreeFrame.subtreeLength(frame);
const descendantsEndIndexExcl = frameIndex + frameReader.subtreeLength(frame);
for (let descendantIndex = frameIndex + 1; descendantIndex < descendantsEndIndexExcl; descendantIndex++) {
const descendantFrame = getTreeFramePtr(frames, descendantIndex);
if (renderTreeFrame.frameType(descendantFrame) === FrameType.attribute) {
this.applyAttribute(componentId, newDomElementRaw, descendantFrame);
const descendantFrame = batch.referenceFramesEntry(frames, descendantIndex);
if (frameReader.frameType(descendantFrame) === FrameType.attribute) {
this.applyAttribute(batch, componentId, newDomElementRaw, descendantFrame);
} else {
// As soon as we see a non-attribute child, all the subsequent child frames are
// not attributes, so bail out and insert the remnants recursively
this.insertFrameRange(componentId, newElement, 0, frames, descendantIndex, descendantsEndIndexExcl);
this.insertFrameRange(batch, componentId, newElement, 0, frames, descendantIndex, descendantsEndIndexExcl);
break;
}
}
}
private insertComponent(parent: LogicalElement, childIndex: number, frame: RenderTreeFramePointer) {
private insertComponent(batch: RenderBatch, parent: LogicalElement, childIndex: number, frame: RenderTreeFrame) {
const containerElement = createAndInsertLogicalContainer(parent, childIndex);
// All we have to do is associate the child component ID with its location. We don't actually
// do any rendering here, because the diff for the child will appear later in the render batch.
const childComponentId = renderTreeFrame.componentId(frame);
const childComponentId = batch.frameReader.componentId(frame);
this.attachComponentToElement(childComponentId, containerElement);
}
private insertText(parent: LogicalElement, childIndex: number, textFrame: RenderTreeFramePointer) {
const textContent = renderTreeFrame.textContent(textFrame)!;
private insertText(batch: RenderBatch, parent: LogicalElement, childIndex: number, textFrame: RenderTreeFrame) {
const textContent = batch.frameReader.textContent(textFrame)!;
const newTextNode = document.createTextNode(textContent);
insertLogicalChild(newTextNode, parent, childIndex);
}
private applyAttribute(componentId: number, toDomElement: Element, attributeFrame: RenderTreeFramePointer) {
const attributeName = renderTreeFrame.attributeName(attributeFrame)!;
private applyAttribute(batch: RenderBatch, componentId: number, toDomElement: Element, attributeFrame: RenderTreeFrame) {
const frameReader = batch.frameReader;
const attributeName = frameReader.attributeName(attributeFrame)!;
const browserRendererId = this.browserRendererId;
const eventHandlerId = renderTreeFrame.attributeEventHandlerId(attributeFrame);
const eventHandlerId = frameReader.attributeEventHandlerId(attributeFrame);
if (eventHandlerId) {
const firstTwoChars = attributeName.substring(0, 2);
@ -210,33 +220,34 @@ export class BrowserRenderer {
}
// First see if we have special handling for this attribute
if (!this.tryApplySpecialProperty(toDomElement, attributeName, attributeFrame)) {
if (!this.tryApplySpecialProperty(batch, toDomElement, attributeName, attributeFrame)) {
// If not, treat it as a regular string-valued attribute
toDomElement.setAttribute(
attributeName,
renderTreeFrame.attributeValue(attributeFrame)!
frameReader.attributeValue(attributeFrame)!
);
}
}
private tryApplySpecialProperty(element: Element, attributeName: string, attributeFrame: RenderTreeFramePointer | null) {
private tryApplySpecialProperty(batch: RenderBatch, element: Element, attributeName: string, attributeFrame: RenderTreeFrame | null) {
switch (attributeName) {
case 'value':
return this.tryApplyValueProperty(element, attributeFrame);
return this.tryApplyValueProperty(batch, element, attributeFrame);
case 'checked':
return this.tryApplyCheckedProperty(element, attributeFrame);
return this.tryApplyCheckedProperty(batch, element, attributeFrame);
default:
return false;
}
}
private tryApplyValueProperty(element: Element, attributeFrame: RenderTreeFramePointer | null) {
private tryApplyValueProperty(batch: RenderBatch, element: Element, attributeFrame: RenderTreeFrame | null) {
// Certain elements have built-in behaviour for their 'value' property
const frameReader = batch.frameReader;
switch (element.tagName) {
case 'INPUT':
case 'SELECT':
case 'TEXTAREA': {
const value = attributeFrame ? renderTreeFrame.attributeValue(attributeFrame) : null;
const value = attributeFrame ? frameReader.attributeValue(attributeFrame) : null;
(element as any).value = value;
if (element.tagName === 'SELECT') {
@ -249,7 +260,7 @@ export class BrowserRenderer {
return true;
}
case 'OPTION': {
const value = attributeFrame ? renderTreeFrame.attributeValue(attributeFrame) : null;
const value = attributeFrame ? frameReader.attributeValue(attributeFrame) : null;
if (value) {
element.setAttribute('value', value);
} else {
@ -258,7 +269,7 @@ export class BrowserRenderer {
// See above for why we have this special handling for <select>/<option>
const parentElement = element.parentElement;
if (parentElement && (selectValuePropname in parentElement) && parentElement[selectValuePropname] === value) {
this.tryApplyValueProperty(parentElement, attributeFrame);
this.tryApplyValueProperty(batch, parentElement, attributeFrame);
delete parentElement[selectValuePropname];
}
return true;
@ -268,10 +279,10 @@ export class BrowserRenderer {
}
}
private tryApplyCheckedProperty(element: Element, attributeFrame: RenderTreeFramePointer | null) {
private tryApplyCheckedProperty(batch: RenderBatch, element: Element, attributeFrame: RenderTreeFrame | null) {
// Certain elements have built-in behaviour for their 'checked' property
if (element.tagName === 'INPUT') {
const value = attributeFrame ? renderTreeFrame.attributeValue(attributeFrame) : null;
const value = attributeFrame ? batch.frameReader.attributeValue(attributeFrame) : null;
(element as any).checked = value !== null;
return true;
} else {
@ -279,30 +290,31 @@ export class BrowserRenderer {
}
}
private insertFrameRange(componentId: number, parent: LogicalElement, childIndex: number, frames: System_Array<RenderTreeFramePointer>, startIndex: number, endIndexExcl: number): number {
private insertFrameRange(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, startIndex: number, endIndexExcl: number): number {
const origChildIndex = childIndex;
for (let index = startIndex; index < endIndexExcl; index++) {
const frame = getTreeFramePtr(frames, index);
const numChildrenInserted = this.insertFrame(componentId, parent, childIndex, frames, frame, index);
const frame = batch.referenceFramesEntry(frames, index);
const numChildrenInserted = this.insertFrame(batch, componentId, parent, childIndex, frames, frame, index);
childIndex += numChildrenInserted;
// Skip over any descendants, since they are already dealt with recursively
index += countDescendantFrames(frame);
index += countDescendantFrames(batch, frame);
}
return (childIndex - origChildIndex); // Total number of children inserted
}
}
function countDescendantFrames(frame: RenderTreeFramePointer): number {
switch (renderTreeFrame.frameType(frame)) {
function countDescendantFrames(batch: RenderBatch, frame: RenderTreeFrame): number {
const frameReader = batch.frameReader;
switch (frameReader.frameType(frame)) {
// The following frame types have a subtree length. Other frames may use that memory slot
// to mean something else, so we must not read it. We should consider having nominal subtypes
// of RenderTreeFramePointer that prevent access to non-applicable fields.
case FrameType.component:
case FrameType.element:
case FrameType.region:
return renderTreeFrame.subtreeLength(frame) - 1;
return frameReader.subtreeLength(frame) - 1;
default:
return 0;
}

View File

@ -1,39 +0,0 @@
import { Pointer, System_Array } from '../Platform/Platform';
import { platform } from '../Environment';
import { RenderTreeFramePointer } from './RenderTreeFrame';
import { RenderTreeEditPointer } from './RenderTreeEdit';
// Keep in sync with the structs in .NET code
export const renderBatch = {
updatedComponents: (obj: RenderBatchPointer) => platform.readStructField<ArrayRangePointer<RenderTreeDiffPointer>>(obj, 0),
referenceFrames: (obj: RenderBatchPointer) => platform.readStructField<ArrayRangePointer<RenderTreeFramePointer>>(obj, arrayRangeStructLength),
disposedComponentIds: (obj: RenderBatchPointer) => platform.readStructField<ArrayRangePointer<number>>(obj, arrayRangeStructLength + arrayRangeStructLength),
disposedEventHandlerIds: (obj: RenderBatchPointer) => platform.readStructField<ArrayRangePointer<number>>(obj, arrayRangeStructLength + arrayRangeStructLength + arrayRangeStructLength),
};
const arrayRangeStructLength = 8;
export const arrayRange = {
array: <T>(obj: ArrayRangePointer<T>) => platform.readObjectField<System_Array<T>>(obj, 0),
count: <T>(obj: ArrayRangePointer<T>) => platform.readInt32Field(obj, 4),
};
const arraySegmentStructLength = 12;
export const arraySegment = {
array: <T>(obj: ArraySegmentPointer<T>) => platform.readObjectField<System_Array<T>>(obj, 0),
offset: <T>(obj: ArraySegmentPointer<T>) => platform.readInt32Field(obj, 4),
count: <T>(obj: ArraySegmentPointer<T>) => platform.readInt32Field(obj, 8),
};
export const renderTreeDiffStructLength = 4 + arraySegmentStructLength;
export const renderTreeDiff = {
componentId: (obj: RenderTreeDiffPointer) => platform.readInt32Field(obj, 0),
edits: (obj: RenderTreeDiffPointer) => platform.readStructField<ArraySegmentPointer<RenderTreeEditPointer>>(obj, 4),
};
// 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<T> extends Pointer { ArrayRangePointer__DO_NOT_IMPLEMENT: any }
export interface ArraySegmentPointer<T> extends Pointer { ArraySegmentPointer__DO_NOT_IMPLEMENT: any }
export interface RenderTreeDiffPointer extends Pointer { RenderTreeDiffPointer__DO_NOT_IMPLEMENT: any }

View File

@ -0,0 +1,82 @@
export interface RenderBatch {
updatedComponents(): ArrayRange<RenderTreeDiff>;
referenceFrames(): ArrayRange<RenderTreeFrame>;
disposedComponentIds(): ArrayRange<number>;
disposedEventHandlerIds(): ArrayRange<number>;
updatedComponentsEntry(values: ArrayValues<RenderTreeDiff>, index: number): RenderTreeDiff;
referenceFramesEntry(values: ArrayValues<RenderTreeFrame>, index: number): RenderTreeFrame;
disposedComponentIdsEntry(values: ArrayValues<number>, index: number): number;
disposedEventHandlerIdsEntry(values: ArrayValues<number>, index: number): number;
diffReader: RenderTreeDiffReader;
editReader: RenderTreeEditReader;
frameReader: RenderTreeFrameReader;
arrayRangeReader: ArrayRangeReader;
arraySegmentReader: ArraySegmentReader;
}
export interface ArrayRangeReader {
count<T>(arrayRange: ArrayRange<T>): number;
values<T>(arrayRange: ArrayRange<T>): ArrayValues<T>;
}
export interface ArraySegmentReader {
offset<T>(arraySegment: ArraySegment<T>): number;
count<T>(arraySegment: ArraySegment<T>): number;
values<T>(arraySegment: ArraySegment<T>): ArrayValues<T>;
}
export interface RenderTreeDiffReader {
componentId(diff: RenderTreeDiff): number;
edits(diff: RenderTreeDiff): ArraySegment<RenderTreeEdit>;
editsEntry(values: ArrayValues<RenderTreeEdit>, index: number): RenderTreeEdit;
}
export interface RenderTreeEditReader {
editType(edit: RenderTreeEdit): EditType;
siblingIndex(edit: RenderTreeEdit): number;
newTreeIndex(edit: RenderTreeEdit): number;
removedAttributeName(edit: RenderTreeEdit): string | null;
}
export interface RenderTreeFrameReader {
frameType(frame: RenderTreeFrame): FrameType;
subtreeLength(frame: RenderTreeFrame): number;
elementReferenceCaptureId(frame: RenderTreeFrame): number;
componentId(frame: RenderTreeFrame): number;
elementName(frame: RenderTreeFrame): string | null;
textContent(frame: RenderTreeFrame): string | null;
attributeName(frame: RenderTreeFrame): string | null;
attributeValue(frame: RenderTreeFrame): string | null;
attributeEventHandlerId(frame: RenderTreeFrame): number;
}
export interface ArrayRange<T> { ArrayRange__DO_NOT_IMPLEMENT: any }
export interface ArraySegment<T> { ArraySegment__DO_NOT_IMPLEMENT: any }
export interface ArrayValues<T> { ArrayValues__DO_NOT_IMPLEMENT: any }
export interface RenderTreeDiff { RenderTreeDiff__DO_NOT_IMPLEMENT: any }
export interface RenderTreeFrame { RenderTreeFrame__DO_NOT_IMPLEMENT: any }
export interface RenderTreeEdit { RenderTreeEdit__DO_NOT_IMPLEMENT: any }
export enum EditType {
// The values must be kept in sync with the .NET equivalent in RenderTreeEditType.cs
prependFrame = 1,
removeFrame = 2,
setAttribute = 3,
removeAttribute = 4,
updateText = 5,
stepIn = 6,
stepOut = 7,
}
export enum FrameType {
// The values must be kept in sync with the .NET equivalent in RenderTreeFrameType.cs
element = 1,
text = 2,
attribute = 3,
component = 4,
region = 5,
elementReferenceCapture = 6,
}

View File

@ -0,0 +1,89 @@
import { platform } from '../../Environment';
import { RenderBatch, ArrayRange, ArrayRangeReader, ArraySegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch';
import { Pointer, System_Array } from '../../Platform/Platform';
// Used when running on Mono WebAssembly for shared-memory interop. The code here encapsulates
// our knowledge of the memory layout of RenderBatch and all referenced types.
//
// In this implementation, all the DTO types are really heap pointers at runtime, hence all
// the casts to 'any' whenever we pass them to platform.read.
export class SharedMemoryRenderBatch implements RenderBatch {
constructor(private batchAddress: Pointer) {
}
// Keep in sync with memory layout in RenderBatch.cs
updatedComponents() { return platform.readStructField<Pointer>(this.batchAddress, 0) as any as ArrayRange<RenderTreeDiff>; }
referenceFrames() { return platform.readStructField<Pointer>(this.batchAddress, arrayRangeReader.structLength) as any as ArrayRange<RenderTreeDiff>; }
disposedComponentIds() { return platform.readStructField<Pointer>(this.batchAddress, arrayRangeReader.structLength * 2) as any as ArrayRange<number>; }
disposedEventHandlerIds() { return platform.readStructField<Pointer>(this.batchAddress, arrayRangeReader.structLength * 3) as any as ArrayRange<number>; }
updatedComponentsEntry(values: ArrayValues<RenderTreeDiff>, index: number) {
return arrayValuesEntry(values, index, diffReader.structLength);
}
referenceFramesEntry(values: ArrayValues<RenderTreeFrame>, index: number) {
return arrayValuesEntry(values, index, frameReader.structLength);
}
disposedComponentIdsEntry(values: ArrayValues<number>, index: number) {
return arrayValuesEntry(values, index, /* int length */ 4);
}
disposedEventHandlerIdsEntry(values: ArrayValues<number>, index: number) {
return arrayValuesEntry(values, index, /* int length */ 4);
}
arrayRangeReader = arrayRangeReader;
arraySegmentReader = arraySegmentReader;
diffReader = diffReader;
editReader = editReader;
frameReader = frameReader;
}
// Keep in sync with memory layout in ArrayRange.cs
const arrayRangeReader = {
structLength: 8,
values: <T>(arrayRange: ArrayRange<T>) => platform.readObjectField<System_Array<T>>(arrayRange as any, 0) as any as ArrayValues<T>,
count: <T>(arrayRange: ArrayRange<T>) => platform.readInt32Field(arrayRange as any, 4),
};
// Keep in sync with memory layout in ArraySegment
const arraySegmentReader = {
structLength: 12,
values: <T>(arraySegment: ArraySegment<T>) => platform.readObjectField<System_Array<T>>(arraySegment as any, 0) as any as ArrayValues<T>,
offset: <T>(arraySegment: ArraySegment<T>) => platform.readInt32Field(arraySegment as any, 4),
count: <T>(arraySegment: ArraySegment<T>) => platform.readInt32Field(arraySegment as any, 8),
};
// Keep in sync with memory layout in RenderTreeDiff.cs
const diffReader = {
structLength: 4 + arraySegmentReader.structLength,
componentId: (diff: RenderTreeDiff) => platform.readInt32Field(diff as any, 0),
edits: (diff: RenderTreeDiff) => platform.readStructField<Pointer>(diff as any, 4) as any as ArraySegment<RenderTreeEdit>,
editsEntry: (values: ArrayValues<RenderTreeEdit>, index: number) => arrayValuesEntry(values, index, editReader.structLength),
};
// Keep in sync with memory layout in RenderTreeEdit.cs
const editReader = {
structLength: 16,
editType: (edit: RenderTreeEdit) => platform.readInt32Field(edit as any, 0) as EditType,
siblingIndex: (edit: RenderTreeEdit) => platform.readInt32Field(edit as any, 4),
newTreeIndex: (edit: RenderTreeEdit) => platform.readInt32Field(edit as any, 8),
removedAttributeName: (edit: RenderTreeEdit) => platform.readStringField(edit as any, 12),
};
// Keep in sync with memory layout in RenderTreeFrame.cs
const frameReader = {
structLength: 28,
frameType: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 4) as FrameType,
subtreeLength: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 8),
elementReferenceCaptureId: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 8),
componentId: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 12),
elementName: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16),
textContent: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16),
attributeName: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16),
attributeValue: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 24),
attributeEventHandlerId: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 8),
};
function arrayValuesEntry<T>(arrayValues: ArrayValues<T>, index: number, itemSize: number): T {
return platform.getArrayEntryPtr(arrayValues as any as System_Array<T>, index, itemSize) as any as T;
}

View File

@ -1,29 +0,0 @@
import { System_Array, Pointer } from '../Platform/Platform';
import { platform } from '../Environment';
const renderTreeEditStructLength = 16;
export function getRenderTreeEditPtr(renderTreeEdits: System_Array<RenderTreeEditPointer>, index: number) {
return platform.getArrayEntryPtr(renderTreeEdits, index, renderTreeEditStructLength);
}
export const renderTreeEdit = {
// The properties and memory layout must be kept in sync with the .NET equivalent in RenderTreeEdit.cs
type: (edit: RenderTreeEditPointer) => platform.readInt32Field(edit, 0) as EditType,
siblingIndex: (edit: RenderTreeEditPointer) => platform.readInt32Field(edit, 4),
newTreeIndex: (edit: RenderTreeEditPointer) => platform.readInt32Field(edit, 8),
removedAttributeName: (edit: RenderTreeEditPointer) => platform.readStringField(edit, 12),
};
export enum EditType {
prependFrame = 1,
removeFrame = 2,
setAttribute = 3,
removeAttribute = 4,
updateText = 5,
stepIn = 6,
stepOut = 7,
}
// Nominal type to ensure only valid pointers are passed to the renderTreeEdit functions.
// At runtime the values are just numbers.
export interface RenderTreeEditPointer extends Pointer { RenderTreeEditPointer__DO_NOT_IMPLEMENT: any }

View File

@ -1,38 +0,0 @@
import { System_String, System_Array, Pointer } from '../Platform/Platform';
import { platform } from '../Environment';
const renderTreeFrameStructLength = 28;
// To minimise GC pressure, instead of instantiating a JS object to represent each tree frame,
// we work in terms of pointers to the structs on the .NET heap, and use static functions that
// know how to read property values from those structs.
export function getTreeFramePtr(renderTreeEntries: System_Array<RenderTreeFramePointer>, index: number) {
return platform.getArrayEntryPtr(renderTreeEntries, index, renderTreeFrameStructLength);
}
export const renderTreeFrame = {
// The properties and memory layout must be kept in sync with the .NET equivalent in RenderTreeFrame.cs
frameType: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 4) as FrameType,
subtreeLength: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8) as FrameType,
elementReferenceCaptureId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8),
componentId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 12),
elementName: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
textContent: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
attributeName: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
attributeValue: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 24),
attributeEventHandlerId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8),
};
export enum FrameType {
// The values must be kept in sync with the .NET equivalent in RenderTreeFrameType.cs
element = 1,
text = 2,
attribute = 3,
component = 4,
region = 5,
elementReferenceCapture = 6,
}
// Nominal type to ensure only valid pointers are passed to the renderTreeFrame functions.
// At runtime the values are just numbers.
export interface RenderTreeFramePointer extends Pointer { RenderTreeFramePointer__DO_NOT_IMPLEMENT: any }

View File

@ -1,6 +1,6 @@
import { System_Object, System_String, System_Array, MethodHandle, Pointer } from '../Platform/Platform';
import { platform } from '../Environment';
import { renderBatch as renderBatchStruct, arrayRange, arraySegment, renderTreeDiffStructLength, renderTreeDiff, RenderBatchPointer, RenderTreeDiffPointer } from './RenderBatch';
import { RenderBatch } from './RenderBatch/RenderBatch';
import { BrowserRenderer } from './BrowserRenderer';
type BrowserRendererRegistry = { [browserRendererId: number]: BrowserRenderer };
@ -20,45 +20,40 @@ export function attachRootComponentToElement(browserRendererId: number, elementS
browserRenderer.attachRootComponentToElement(componentId, element);
}
export function renderBatch(browserRendererId: number, batch: RenderBatchPointer) {
export function renderBatch(browserRendererId: number, batch: RenderBatch) {
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);
const referenceFramesStruct = renderBatchStruct.referenceFrames(batch);
const referenceFrames = arrayRange.array(referenceFramesStruct);
const arrayRangeReader = batch.arrayRangeReader;
const updatedComponentsRange = batch.updatedComponents();
const updatedComponentsValues = arrayRangeReader.values(updatedComponentsRange);
const updatedComponentsLength = arrayRangeReader.count(updatedComponentsRange);
const referenceFrames = batch.referenceFrames();
const referenceFramesValues = arrayRangeReader.values(referenceFrames);
const diffReader = batch.diffReader;
for (let i = 0; i < updatedComponentsLength; i++) {
const diff = platform.getArrayEntryPtr(updatedComponentsArray, i, renderTreeDiffStructLength);
const componentId = renderTreeDiff.componentId(diff);
const editsArraySegment = renderTreeDiff.edits(diff);
const edits = arraySegment.array(editsArraySegment);
const editsOffset = arraySegment.offset(editsArraySegment);
const editsLength = arraySegment.count(editsArraySegment);
browserRenderer.updateComponent(componentId, edits, editsOffset, editsLength, referenceFrames);
const diff = batch.updatedComponentsEntry(updatedComponentsValues, i);
const componentId = diffReader.componentId(diff);
const edits = diffReader.edits(diff);
browserRenderer.updateComponent(batch, componentId, edits, referenceFramesValues);
}
const disposedComponentIds = renderBatchStruct.disposedComponentIds(batch);
const disposedComponentIdsLength = arrayRange.count(disposedComponentIds);
const disposedComponentIdsArray = arrayRange.array(disposedComponentIds);
const disposedComponentIdsRange = batch.disposedComponentIds();
const disposedComponentIdsValues = arrayRangeReader.values(disposedComponentIdsRange);
const disposedComponentIdsLength = arrayRangeReader.count(disposedComponentIdsRange);
for (let i = 0; i < disposedComponentIdsLength; i++) {
const componentIdPtr = platform.getArrayEntryPtr(disposedComponentIdsArray, i, 4);
const componentId = platform.readInt32Field(componentIdPtr);
const componentId = batch.disposedComponentIdsEntry(disposedComponentIdsValues, i);
browserRenderer.disposeComponent(componentId);
}
const disposedEventHandlerIds = renderBatchStruct.disposedEventHandlerIds(batch);
const disposedEventHandlerIdsLength = arrayRange.count(disposedEventHandlerIds);
const disposedEventHandlerIdsArray = arrayRange.array(disposedEventHandlerIds);
const disposedEventHandlerIdsRange = batch.disposedEventHandlerIds();
const disposedEventHandlerIdsValues = arrayRangeReader.values(disposedComponentIdsRange);
const disposedEventHandlerIdsLength = arrayRangeReader.count(disposedEventHandlerIdsRange);
for (let i = 0; i < disposedEventHandlerIdsLength; i++) {
const eventHandlerIdPtr = platform.getArrayEntryPtr(disposedEventHandlerIdsArray, i, 4);
const eventHandlerId = platform.readInt32Field(eventHandlerIdPtr);
const eventHandlerId = batch.disposedEventHandlerIdsEntry(disposedEventHandlerIdsValues, i);
browserRenderer.disposeEventHandler(eventHandlerId);
}
}