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:
parent
389ff14a82
commit
b869ced648
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
@ -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 }
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue