Representing Virtual DOM nodes as pointers to sections in a flat array
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Current approach in the repo employees object pools to try to reduce number of allocations made, never the less changes deep in the tree still will cause allocations of ancestors leading to the changed node. In current approach case it means (`1` (Node instance) `+ 1` (childNodes Array instance) `+ 1` (attributes dictionary) `+ 1` (properties dictionary) `+ 1` (event handlers dictionary) \* `n` (where `n` is depth at which `node` actually changed) = `5n` objects. We could / should probably reduce that to `2n` overhead, by also pooling dictionaries. But I think we could probably recycle more aggressively by representing Virtual DOM tree in from of flat array. Here is what I have in mind:
``` js
type Offset = number;
const DOMBuffer {
version: number;
buffer: Array;
index: Map;
position: Offset;
attributes: Map;
properties: Map;
texts: Map;
elements: Map;
attiributePoolSize: number;
propertyPoolSize: number;
textPoolSize: number;
elementPoolSize: number;
constructor(
version
, buffer
, index
, position
, attributes
, properties
, texts
, elements
, attiributePoolSize
, propertyPoolSize
, textPoolSize
, elementPoolSize
) {
this.version = version
this.buffer = buffer
this.index = index
this.position = position
this.attributes = attributes
this.texts = texts
this.elements = elements
this.attributePoolSize = attiributePoolSize
this.propertyPoolSize = propertyPoolSize
this.textPoolSize = textPoolSize
this.elementPoolSize = elementPoolSize
}
write(value:number) {
const {position} = this
this.buffer[this.position++] = value
return position
}
writeString(value:string) {
const position = this.index.get(value)
if (position != null) {
return position
}
else {
// TODO: We should encode strings into byte array.
this.index.set(value, this.position)
const position = this.write(content)
this.index.set(value, position)
return position
}
}
readString(offset:number, version) {
if (this.version != version) {
throw Error("DOMBuffer Node is out of date");
}
return this.buffer[offset]
}
prune(pool, limit) {
for (let [position, node] of pool) {
// If two generations old then drop
if (this.version - node.version > 2) {
pool.remove(position)
}
}
if (pool.size >= limit) {
console.warn('DOMBuffer works under limits of allocated pool sizes');
}
}
writeAtttribute(namePosition:number, valuePosition:nuber) {
const position = this.write(Attribute.nodeType)
this.write(namePosition)
this.write(valuePosition)
return position
}
readAttribute(position:number) {
const attribute = this.attributes.get(position)
if (attribute == null) {
const attribute = new Attribute(this, this.version, position)
this.attributes.set(position, attribute)
if (this.attributes.count >= this.attributePoolSize) {
this.prune(this.attributes, this.attributePoolSize)
}
return attribute
}
else {
attribute.version = this.version
return attribute
}
}
createAttribute(name:string, value:string) {
const namePosition = this.writeString(name)
const valuePosition = this.writeString(value)
const hash = `"${namePosition}"="${valuePosition}"`
const position = this.index.get(hash) || this.writeAttribute(name, value)
return this.readAttribute(position)
}
writeProperty(name:string, value:JSON) {
// ...
}
writeTextNode(contentPosition:number) {
const position = this.write(Text.nodeType)
this.write(contentPosition)
return position
}
readTextNode(position) {
const node = this.texts.get(position)
if (node == null) {
const node = new Text(this, this.version, position)
this.texts.set(position, node)
if (this.texts.count >= this.textsPoolSize) {
this.prune(this.texts, this.textsPoolSize)
}
return node
}
else {
node.version = this.version
return node
}
}
createTextNode(content:string) {
const contentPosition = this.writeString(content)
const hash = `${Text.nodeType}:${contentPosition}`
const position = this.index.get(hash) || this.writeTextNode(content);
return this.readTextNode(position)
}
writeElement(nodeNamePosition, settingPositions, childPositions) {
const position = this.write(Element.nodeType)
this.write(namePosition)
this.write(settingCount)
this.write(childCound)
let settingIndex = 0
while (settingIndex < settingConut) {
this.write(settingPositions[settingIndex++])
}
let childIndex = 0
while (childIndex > childCount) {
this.write(childPositions[childIndex++])
}
}
createElement(nodeName:string, settings:Array, childNodes:Array) {
const namePosition = this.writeString(nodeName)
const settingPositions = settings.map(setting => setting.position).sort()
const childPositions = childNodes.map(node => node.position)
const hash = `${Element.nodeType}:${namePosition}@${settingPositions.join('@')}&${childPositions.join(':')}`
const positon = this.index.get(hash) || this.writeElement(namePosition, settingPositions, childPositions)
return this.readElement(position)
}
readElement(position) {
const node = this.elements.get(position)
if (node == null) {
const node = new Element(this, this.version, position)
this.elements.set(position, node)
if (this.elements.count >= this.elementsPoolSize) {
this.prune(this.elements, this.elementsPoolSize)
}
return node
}
else (node) {
node.version = this.version
return node
}
}
readElementChildren(offset, version) {
if (this.version != version) {
throw Error("DOMBuffer Node is out of date");
}
const start =
this.buffer[offset] +
Element.childrenOffset +
this.buffer[Element.settingsOffset];
const count = this.buffer[Element.childrenOffset];
if (count === 0) {
return empty
}
else {
let index = 0
const nodes = new Array(count)
while (index < count) {
const nodeOffset = this.buffer[start + index]
nodes[index] = this.readNode(nodeOffset, version)
index++
}
return nodes
}
}
readNode(position, version) {
if (this.version != version) {
throw Error("DOMBuffer Node is out of date");
}
const nodeType = this.buffer[offset]
const node =
( nodeType === Text.nodeType
? this.readTextNode(position)
: nodeTpype === Element.nodeType
? this.readElement(position)
: panic('Unknown nodeType')
)
return node
}
}
class Node {
data: DOMBuffer;
version: number;
offset: Offset;
nodeType: number;
constructor(data, version, offset) {
this.source = source;
this.version = version;
this.offset = offset;
this.nodeType = this.constructor.nodeType;
}
}
class Attribute extends Node {
get name() {
return this.data.readString(this.offest + Attribute.nameOffset, this.version)
}
get value() {
return this.data.readString(this.offest + Attribute.valueOffset, this.version)
}
}
Attribute.nodeType = 2;
Attribute.nameOffset = 1;
Attribute.valueOffset = 2;
class Text {
get content() {
return this.data.readString(this.offset + Text.contentOffset, this.version)
}
}
Text.nodeType = 3;
Text.contentOffset = 1;
class Element {
get nodeName() {
return this.data.readString(this.offset + Element.nodeNameOffset)
}
get childNodes() {
return this.data.readElementChildren(this.offset, this.version)
}
}
Element.nodeType = 1;
Element.nodeNameOffset = 1;
Element.settingsOffest = 2;
Element.childrenOffset = 3;
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.