@pmndrs/uikit
Version:
Build performant 3D user interfaces with Three.js and yoga.
185 lines (184 loc) • 7.85 kB
JavaScript
export function assureBucketExists(buckets, bucketIndex) {
while (bucketIndex >= buckets.length) {
let offset = 0;
let missingSpace = 0;
if (buckets.length > 0) {
const prevBucket = buckets[buckets.length - 1];
offset += prevBucket.offset + prevBucket.elements.length;
missingSpace = Math.min(0, prevBucket.missingSpace); //taking only the exceeding space
prevBucket.missingSpace -= missingSpace;
}
buckets.push({
add: [],
missingSpace,
offset,
elements: [],
});
}
}
export function resizeSortedBucketsSpace(buckets, oldSize, newSize) {
assureBucketExists(buckets, 0);
//add new space to last bucket
const lastBucket = buckets[buckets.length - 1];
lastBucket.missingSpace += oldSize - newSize;
}
/**
* @returns true iff a call to @function updateSortedBucketsAllocation is necassary
*/
export function addToSortedBuckets(buckets, bucketIndex, element, activateElement) {
assureBucketExists(buckets, bucketIndex);
const bucket = buckets[bucketIndex];
bucket.missingSpace += 1;
if (bucket.missingSpace <= 0) {
//bucket has still room at the end => just place the element there
activateElement(element, bucket, bucket.elements.length);
bucket.elements.push(element);
return false;
}
bucket.add.push(element);
return true;
}
/**
* assures that the free space of a bucket is always at the end
* @returns true iff a call to @function updateSortedBucketsAllocation is necassary
*/
export function removeFromSortedBuckets(buckets, bucketIndex, element, elementIndex, activateElement, clearBufferAt, setElementIndex, bufferCopyWithin) {
if (bucketIndex >= buckets.length) {
throw new Error(`no bucket at index ${bucketIndex}`);
}
const bucket = buckets[bucketIndex];
bucket.missingSpace -= 1;
const addIndex = bucket.add.indexOf(element);
if (addIndex != -1) {
bucket.add.splice(addIndex, 1);
return false;
}
if (elementIndex == null || elementIndex >= bucket.elements.length) {
throw new Error(`no element at index ${elementIndex}`);
}
if (bucket.add.length > 0) {
//replace
const newElement = bucket.add.shift();
bucket.elements[elementIndex] = newElement;
activateElement(newElement, bucket, elementIndex);
return false;
}
const offset = bucket.offset;
const lastIndexInBucket = bucket.elements.length - 1;
if (lastIndexInBucket != elementIndex) {
//element not the last element => need to be moved to the end
const startIndex = offset + lastIndexInBucket;
const targetIndex = offset + elementIndex;
bufferCopyWithin(targetIndex, startIndex, startIndex + 1);
const swapElement = bucket.elements[lastIndexInBucket];
bucket.elements[elementIndex] = swapElement;
setElementIndex(swapElement, elementIndex);
}
clearBufferAt(offset + lastIndexInBucket);
bucket.elements.length -= 1;
if (bucketIndex < buckets.length - 1) {
return true;
}
//we are at the last bucket => merge missing space with the previous bucket(s)
let currentBucket = bucket;
while (currentBucket.elements.length === 0 && currentBucket.add.length == 0 && bucketIndex > 0) {
const prevBucket = buckets[bucketIndex - 1];
prevBucket.missingSpace += currentBucket.missingSpace;
currentBucket = buckets[--bucketIndex];
}
buckets.length = bucketIndex + 1;
return false;
}
/**
* @requires that the buffer has room for elementCount number of elements
*/
export function updateSortedBucketsAllocation(buckets, activateElement, bufferCopyWithin) {
let bucketsLength = buckets.length;
let lastBucketWithElements = -1;
for (let i = 0; i < bucketsLength; i++) {
const bucket = buckets[i];
if (bucket.elements.length + bucket.add.length > 0) {
//bucket will have more than 0 elements after this
lastBucketWithElements = i;
}
const lastBucket = i === bucketsLength - 1;
if (!lastBucket && bucket.missingSpace === 0) {
continue;
}
//find shift partner - TODO: use skip list (negative buckets, positive buckets)
const hasSpace = bucket.missingSpace < 0;
for (let ii = i - 1; ii >= 0; ii--) {
//2 cases:
// 1. one has space and the other needs space
// 2. both have space
const otherBucket = buckets[ii];
if (otherBucket.missingSpace === 0) {
continue;
}
const otherHasSpace = otherBucket.missingSpace < 0;
if (otherHasSpace && (lastBucket || hasSpace)) {
//case 2 - both have space: merge space into bucket by shifting to other bucket (so that the hole is increased at the end of the bucket)
shiftLeft(buckets, bufferCopyWithin, ii, i, Math.abs(otherBucket.missingSpace));
continue;
}
if (!hasSpace && !otherHasSpace) {
continue;
}
//case 1 - bucket has space the other needs space: shift to the one with space
const shiftBy = Math.min(Math.abs(otherBucket.missingSpace), Math.abs(bucket.missingSpace));
if (hasSpace) {
//shift to bucket to increase the space at the other bucket (so that the hole is increased at the end of the other bucket)
shiftRight(buckets, bufferCopyWithin, ii, i, shiftBy);
}
else {
//shift to other bucket to increase the space at the bucket (so that the hole is increased at the end of the bucket)
shiftLeft(buckets, bufferCopyWithin, ii, i, shiftBy);
}
}
}
const newLastBucket = buckets[lastBucketWithElements];
for (let i = lastBucketWithElements + 1; i < bucketsLength; i++) {
newLastBucket.missingSpace += buckets[i].missingSpace;
}
bucketsLength = buckets.length = lastBucketWithElements + 1;
//add elements at the end of the elements of the buckets
for (let i = 0; i < bucketsLength; i++) {
const bucket = buckets[i];
const { elements, add } = bucket;
const addLength = add.length;
for (let ii = 0; ii < addLength; ii++) {
const element = add[ii];
activateElement(element, bucket, elements.length);
elements.push(element);
}
add.length = 0;
}
}
function shiftLeft(buckets, bufferCopyWithin, startIndexIncl, endIndexIncl, shiftBy) {
const endBucket = buckets[endIndexIncl];
const startIndex = buckets[startIndexIncl + 1].offset;
//array shifting
bufferCopyWithin(startIndex - shiftBy, startIndex, endBucket.offset + endBucket.elements.length);
//updating delta and panel array
const startBucket = buckets[startIndexIncl];
startBucket.missingSpace += shiftBy;
endBucket.missingSpace -= shiftBy;
//updating the offsets
for (let i = startIndexIncl + 1; i <= endIndexIncl; i++) {
buckets[i].offset -= shiftBy;
}
}
function shiftRight(buckets, bufferCopyWithin, startIndexIncl, endIndexIncl, shiftBy) {
const endBucket = buckets[endIndexIncl];
const startIndex = buckets[startIndexIncl + 1].offset;
//array shifting
bufferCopyWithin(startIndex + shiftBy, startIndex, endBucket.offset + endBucket.elements.length);
//updating delta and panel array
const startBucket = buckets[startIndexIncl];
startBucket.missingSpace -= shiftBy;
endBucket.missingSpace += shiftBy;
//updating the offsets
for (let i = startIndexIncl + 1; i <= endIndexIncl; i++) {
buckets[i].offset += shiftBy;
}
}