Skip to content

Commit

Permalink
add partial update support to AttributeManager
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoji Chen committed May 15, 2019
1 parent 9ffe6df commit 44175af
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 10 deletions.
6 changes: 5 additions & 1 deletion docs/api-reference/attribute-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Takes a single parameter as a map of attribute descriptor objects:
* keys are attribute names
* values are objects with attribute definitions:
+ `size` (Number) - number of elements per object
+ `accessor` (String | Array of strings) - accessor name(s) that will
+ `accessor` (String | Array of strings | Function) - accessor name(s) that will
trigger an update of this attribute when changed. Used with
[`updateTriggers`](/docs/api-reference/layer.md#-updatetriggers-object-optional-).
+ `update` (Function) - the function to be called when data changes
Expand Down Expand Up @@ -118,6 +118,8 @@ attributeManager.update({
data,
numInstances,
transitions,
startIndex,
endIndex,
props = {},
buffers = {},
context = {},
Expand All @@ -132,6 +134,8 @@ Parameters:
* `buffers` (Object) - pre-allocated buffers
* `props` (Object) - passed to updaters
* `context` (Object) - Used as "this" context for updaters
* `startIndex` (Number) - start index in the data object, if only a subset of data needs to be considered in the update
* `endIndex` (Number) - end index in the data object, if only a subset of data needs to be considered in the update

Notes:

Expand Down
13 changes: 12 additions & 1 deletion modules/core/src/lib/attribute-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ export default class AttributeManager {
numInstances,
bufferLayout,
transitions,
startIndex,
endIndex,
props = {},
buffers = {},
context = {}
Expand All @@ -238,7 +240,16 @@ export default class AttributeManager {
// Attribute is using generic value from the props
} else if (attribute.needsUpdate()) {
updated = true;
this._updateAttribute({attribute, numInstances, bufferLayout, data, props, context});
this._updateAttribute({
attribute,
numInstances,
bufferLayout,
data,
startIndex,
endIndex,
props,
context
});
}

this.needsRedraw |= attribute.needsRedraw();
Expand Down
40 changes: 32 additions & 8 deletions modules/core/src/lib/attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,29 +184,35 @@ export default class Attribute extends BaseAttribute {
return false;
}

updateBuffer({numInstances, bufferLayout, data, props, context}) {
updateBuffer({numInstances, bufferLayout, data, startIndex, endIndex, props, context}) {
if (!this.needsUpdate()) {
return false;
}

const state = this.userData;

const {update, noAlloc} = state;
const {update} = state;

let updated = true;
if (update) {
// Custom updater - typically for non-instanced layers
update.call(context, this, {data, props, numInstances, bufferLayout});
if (noAlloc || this.constant || !this.buffer) {
update.call(context, this, {data, startIndex, endIndex, props, numInstances, bufferLayout});
if (this.constant || !this.buffer || this.buffer.byteLength < this.value.byteLength) {
// Full update
this.update({
value: this.value,
constant: this.constant
});
} else {
const startOffset = Number.isFinite(startIndex) ? this._getVertexOffset(startIndex) : 0;
const endOffset = Number.isFinite(endIndex)
? this._getVertexOffset(endIndex)
: numInstances * this.size;

// Only update the changed part of the attribute
this.buffer.subData({
data: this.value.subarray(0, numInstances * this.size)
data: this.value.subarray(startOffset, endOffset),
offset: startOffset * this.value.BYTES_PER_ELEMENT
});
}
this._checkAttributeArray();
Expand Down Expand Up @@ -295,6 +301,12 @@ export default class Attribute extends BaseAttribute {
}

// PRIVATE HELPER METHODS
_getVertexOffset(index, bufferLayout = this.bufferLayout) {
if (bufferLayout) {
return bufferLayout.reduce((sum, cur, idx) => (idx < index ? sum + cur : sum), 0) * this.size;
}
return index * this.size;
}

/* check user supplied values and apply fallback */
_normalizeValue(value, out = [], start = 0) {
Expand Down Expand Up @@ -329,7 +341,10 @@ export default class Attribute extends BaseAttribute {
return true;
}

_standardAccessor(attribute, {data, props, numInstances, bufferLayout}) {
_standardAccessor(
attribute,
{data, startIndex = 0, endIndex = Infinity, props, numInstances, bufferLayout}
) {
const state = attribute.userData;

const {accessor} = state;
Expand All @@ -341,12 +356,21 @@ export default class Attribute extends BaseAttribute {
let i = 0;
const {iterable, objectInfo} = createIterable(data);
for (const object of iterable) {
objectInfo.index++;
const objectIndex = ++objectInfo.index;

if (objectIndex < startIndex) {
i += (bufferLayout ? bufferLayout[objectIndex] : 1) * size;
continue; // eslint-disable-line
}
if (objectIndex >= endIndex) {
break;
}

const objectValue = accessorFunc(object, objectInfo);

if (bufferLayout) {
attribute._normalizeValue(objectValue, objectInfo.target);
const numVertices = bufferLayout[objectInfo.index];
const numVertices = bufferLayout[objectIndex];
fillArray({
target: attribute.value,
source: objectInfo.target,
Expand Down
102 changes: 102 additions & 0 deletions test/modules/core/lib/attribute.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,108 @@ test('Attribute#updateBuffer', t => {
t.end();
});

test('Attribute#updateBuffer - partial', t => {
let accessorCalled = 0;

const TEST_PROPS = {
data: [{id: 'A'}, {id: 'B'}, {id: 'C'}, {id: 'D'}],
getValue: d => accessorCalled++
};

const ATTRIBUTE_1 = new Attribute(gl, {
id: 'values-1',
type: GL.FLOAT,
size: 1,
accessor: 'getValue'
});

const ATTRIBUTE_2 = new Attribute(gl, {
id: 'values-2',
type: GL.FLOAT,
size: 1,
accessor: 'getValue'
});

const TEST_CASES = [
{
title: 'full update',
attribute: ATTRIBUTE_1,
params: {
numInstances: 4
},
value: [0, 1, 2, 3]
},
{
title: 'update with startIndex only',
attribute: ATTRIBUTE_1,
params: {
numInstances: 4,
startIndex: 3
},
value: [0, 1, 2, 0]
},
{
title: 'update with index range',
attribute: ATTRIBUTE_1,
params: {
numInstances: 4,
startIndex: 1,
endIndex: 3
},
value: [0, 0, 1, 0]
},
{
title: 'full update - variable size',
attribute: ATTRIBUTE_2,
params: {
numInstances: 10,
bufferLayout: [2, 1, 4, 3]
},
value: [0, 0, 1, 2, 2, 2, 2, 3, 3, 3]
},
{
title: 'update with startIndex only - variable size',
attribute: ATTRIBUTE_2,
params: {
numInstances: 10,
bufferLayout: [2, 1, 4, 3],
startIndex: 3
},
value: [0, 0, 1, 2, 2, 2, 2, 0, 0, 0]
},
{
title: 'update with index range - variable size',
attribute: ATTRIBUTE_2,
params: {
numInstances: 10,
bufferLayout: [2, 1, 4, 3],
startIndex: 1,
endIndex: 3
},
value: [0, 0, 0, 1, 1, 1, 1, 0, 0, 0]
}
];

for (const testCase of TEST_CASES) {
const {attribute} = testCase;
attribute.setNeedsUpdate(true);

// reset stats
accessorCalled = 0;

attribute.allocate(testCase.params.numInstances);
attribute.updateBuffer({
...testCase.params,
data: TEST_PROPS.data,
props: TEST_PROPS
});

t.deepEqual(attribute.value, testCase.value, `${testCase.title} yields correct result`);
}

t.end();
});

// t.ok(attribute.allocate(attributeName, allocCount), 'Attribute.allocate function available');
// t.ok(attribute._setExternalBuffer(attributeName, buffer, numInstances), 'Attribute._setExternalBuffer function available');
// t.ok(attribute._analyzeBuffer(attributeName, numInstances), 'Attribute._analyzeBuffer function available');
Expand Down

0 comments on commit 44175af

Please sign in to comment.