Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 56 additions & 13 deletions src/core/object-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ class ObjectPool {
*/
_count = 0;

/**
* A map from object references to their index in `_pool`. This is used to determine
* whether an object is actually allocated from this pool (and at which index).
*
* @type {WeakMap<InstanceType<T>, number>}
* @private
*/
_objToIndexMap = new WeakMap();

/**
* @param {T} constructorFunc - The constructor function for the
* objects in the pool.
Expand All @@ -40,18 +49,6 @@ class ObjectPool {
this._resize(size);
}

/**
* @param {number} size - The number of object instances to allocate.
* @private
*/
_resize(size) {
if (size > this._pool.length) {
for (let i = this._pool.length; i < size; i++) {
this._pool[i] = new this._constructor();
}
}
}

/**
* Returns an object instance from the pool. If no instances are available, the pool will be
* doubled in size and a new instance will be returned.
Expand All @@ -60,18 +57,64 @@ class ObjectPool {
*/
allocate() {
if (this._count >= this._pool.length) {
this._resize(this._pool.length * 2);
this._resize(Math.max(1, this._pool.length * 2));
}
return this._pool[this._count++];
}

/**
* Attempts to free the given object back into the pool. This only works if the object
* was previously allocated and is still in use.
*
* @param {InstanceType<T>} obj - The object instance to be freed back into the pool.
* @returns {boolean} Whether freeing succeeded.
*/
free(obj) {
const index = this._objToIndexMap.get(obj);
if (index === undefined) {
return false;
}

if (index >= this._count) {
return false;
}

// Swap this object with the last allocated object, then decrement `_count`
const lastIndex = this._count - 1;
const lastObj = this._pool[lastIndex];

this._pool[index] = lastObj;
this._pool[lastIndex] = obj;

this._objToIndexMap.set(lastObj, index);
this._objToIndexMap.set(obj, lastIndex);

this._count -= 1;
return true;
}

/**
* All object instances in the pool will be available again. The pool itself will not be
* resized.
*/
freeAll() {
this._count = 0;
}

/**
* @param {number} size - The number of object instances to allocate.
* @private
*/
_resize(size) {
if (size > this._pool.length) {
for (let i = this._pool.length; i < size; i++) {
const obj = new this._constructor();
this._pool[i] = obj;

this._objToIndexMap.set(obj, i);
}
}
}
}

export { ObjectPool };
131 changes: 131 additions & 0 deletions src/core/queue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* A circular queue that automatically extends its capacity when full.
* This implementation uses a fixed-size array to store elements and
* supports efficient enqueue and dequeue operations.
* It is recommended to use `initialCapacity` that is close to **real-world** usage.
* @template T
*/
class Queue {
/**
* Create a new queue.
* @param {number} [initialCapacity] - The initial capacity of the queue.
*/
constructor(initialCapacity = 8) {
/**
* Underlying storage for the queue.
* @type {Array<T|undefined>}
* @private
*/
this._storage = new Array(initialCapacity);

/**
* The head (front) index.
* @type {number}
* @private
*/
this._head = 0;

/**
* The current number of elements in the queue.
* @type {number}
* @private
*/
this._length = 0;
}

/**
* The current number of elements in the queue.
* @type {number}
* @readonly
*/
get length() {
return this._length;
}

/**
* Change the capacity of the underlying storage.
* Does not shrink capacity if new capacity is less than or equal to the current length.
* @param {number} capacity - The new capacity for the queue.
*/
set capacity(capacity) {
if (capacity <= this._length) {
return;
}

const oldCapacity = this._storage.length;
this._storage.length = capacity;

// Handle wrap-around scenario by moving elements.
if (this._head + this._length > oldCapacity) {
const endLength = oldCapacity - this._head;
for (let i = 0; i < endLength; i++) {
this._storage[capacity - endLength + i] = this._storage[this._head + i];
}
this._head = capacity - endLength;
}
}

/**
* The capacity of the queue.
* @type {number}
* @readonly
*/
get capacity() {
return this._storage.length;
}

/**
* Enqueue (push) a value to the back of the queue.
* Automatically extends capacity if the queue is full.
* @param {T} value - The value to enqueue.
* @returns {number} The new length of the queue.
*/
enqueue(value) {
if (this._length === this._storage.length) {
this.capacity = this._storage.length * 2;
}

const tailIndex = (this._head + this._length) % this._storage.length;
this._storage[tailIndex] = value;
this._length++;
return this._length;
}

/**
* Dequeue (pop) a value from the front of the queue.
* @returns {T|undefined} The dequeued value, or `undefined` if the queue is empty.
*/
dequeue() {
if (this.isEmpty()) {
return undefined;
}

const value = this._storage[this._head];
this._storage[this._head] = undefined;
this._head = (this._head + 1) % this._storage.length;
this._length--;

return value;
}

/**
* Returns the value at the front of the queue without removing it.
* @returns {T|undefined} The front value, or `undefined` if the queue is empty.
*/
peek() {
if (this.isEmpty()) {
return undefined;
}
return this._storage[this._head];
}

/**
* Determines whether the queue is empty.
* @returns {boolean} True if the queue is empty, false otherwise.
*/
isEmpty() {
return this._length === 0;
}
}

export { Queue };
2 changes: 1 addition & 1 deletion src/framework/lightmapper/lightmapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ class Lightmapper {

const meshInstances = bakeNode.meshInstances;
for (let i = 0; i < meshInstances.length; i++) {
if (meshInstances[i]._isVisible(shadowCam)) {
if (meshInstances[i]._isVisible(shadowCam, this.renderer._aabbUpdateIndex)) {
nodeVisible = true;
break;
}
Expand Down
Loading
Loading