mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			237 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			237 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| /*
 | |
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | |
| 	Author Tobias Koppers @sokra
 | |
| */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const { first } = require("./SetHelpers");
 | |
| const SortableSet = require("./SortableSet");
 | |
| 
 | |
| /**
 | |
|  * Multi layer bucket sorted set:
 | |
|  * Supports adding non-existing items (DO NOT ADD ITEM TWICE),
 | |
|  * Supports removing exiting items (DO NOT REMOVE ITEM NOT IN SET),
 | |
|  * Supports popping the first items according to defined order,
 | |
|  * Supports iterating all items without order,
 | |
|  * Supports updating an item in an efficient way,
 | |
|  * Supports size property, which is the number of items,
 | |
|  * Items are lazy partially sorted when needed
 | |
|  * @template T
 | |
|  * @template K
 | |
|  */
 | |
| class LazyBucketSortedSet {
 | |
| 	/**
 | |
| 	 * @param {function(T): K} getKey function to get key from item
 | |
| 	 * @param {function(K, K): number} comparator comparator to sort keys
 | |
| 	 * @param  {...((function(T): any) | (function(any, any): number))} args more pairs of getKey and comparator plus optional final comparator for the last layer
 | |
| 	 */
 | |
| 	constructor(getKey, comparator, ...args) {
 | |
| 		this._getKey = getKey;
 | |
| 		this._innerArgs = args;
 | |
| 		this._leaf = args.length <= 1;
 | |
| 		this._keys = new SortableSet(undefined, comparator);
 | |
| 		/** @type {Map<K, LazyBucketSortedSet<T, any> | SortableSet<T>>} */
 | |
| 		this._map = new Map();
 | |
| 		this._unsortedItems = new Set();
 | |
| 		this.size = 0;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {T} item an item
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	add(item) {
 | |
| 		this.size++;
 | |
| 		this._unsortedItems.add(item);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {K} key key of item
 | |
| 	 * @param {T} item the item
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	_addInternal(key, item) {
 | |
| 		let entry = this._map.get(key);
 | |
| 		if (entry === undefined) {
 | |
| 			entry = this._leaf
 | |
| 				? new SortableSet(undefined, this._innerArgs[0])
 | |
| 				: new /** @type {any} */ (LazyBucketSortedSet)(...this._innerArgs);
 | |
| 			this._keys.add(key);
 | |
| 			this._map.set(key, entry);
 | |
| 		}
 | |
| 		entry.add(item);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {T} item an item
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	delete(item) {
 | |
| 		this.size--;
 | |
| 		if (this._unsortedItems.has(item)) {
 | |
| 			this._unsortedItems.delete(item);
 | |
| 			return;
 | |
| 		}
 | |
| 		const key = this._getKey(item);
 | |
| 		const entry = this._map.get(key);
 | |
| 		entry.delete(item);
 | |
| 		if (entry.size === 0) {
 | |
| 			this._deleteKey(key);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {K} key key to be removed
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	_deleteKey(key) {
 | |
| 		this._keys.delete(key);
 | |
| 		this._map.delete(key);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @returns {T | undefined} an item
 | |
| 	 */
 | |
| 	popFirst() {
 | |
| 		if (this.size === 0) return undefined;
 | |
| 		this.size--;
 | |
| 		if (this._unsortedItems.size > 0) {
 | |
| 			for (const item of this._unsortedItems) {
 | |
| 				const key = this._getKey(item);
 | |
| 				this._addInternal(key, item);
 | |
| 			}
 | |
| 			this._unsortedItems.clear();
 | |
| 		}
 | |
| 		this._keys.sort();
 | |
| 		const key = first(this._keys);
 | |
| 		const entry = this._map.get(key);
 | |
| 		if (this._leaf) {
 | |
| 			const leafEntry = /** @type {SortableSet<T>} */ (entry);
 | |
| 			leafEntry.sort();
 | |
| 			const item = first(leafEntry);
 | |
| 			leafEntry.delete(item);
 | |
| 			if (leafEntry.size === 0) {
 | |
| 				this._deleteKey(key);
 | |
| 			}
 | |
| 			return item;
 | |
| 		} else {
 | |
| 			const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry);
 | |
| 			const item = nodeEntry.popFirst();
 | |
| 			if (nodeEntry.size === 0) {
 | |
| 				this._deleteKey(key);
 | |
| 			}
 | |
| 			return item;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {T} item to be updated item
 | |
| 	 * @returns {function(true=): void} finish update
 | |
| 	 */
 | |
| 	startUpdate(item) {
 | |
| 		if (this._unsortedItems.has(item)) {
 | |
| 			return remove => {
 | |
| 				if (remove) {
 | |
| 					this._unsortedItems.delete(item);
 | |
| 					this.size--;
 | |
| 					return;
 | |
| 				}
 | |
| 			};
 | |
| 		}
 | |
| 		const key = this._getKey(item);
 | |
| 		if (this._leaf) {
 | |
| 			const oldEntry = /** @type {SortableSet<T>} */ (this._map.get(key));
 | |
| 			return remove => {
 | |
| 				if (remove) {
 | |
| 					this.size--;
 | |
| 					oldEntry.delete(item);
 | |
| 					if (oldEntry.size === 0) {
 | |
| 						this._deleteKey(key);
 | |
| 					}
 | |
| 					return;
 | |
| 				}
 | |
| 				const newKey = this._getKey(item);
 | |
| 				if (key === newKey) {
 | |
| 					// This flags the sortable set as unordered
 | |
| 					oldEntry.add(item);
 | |
| 				} else {
 | |
| 					oldEntry.delete(item);
 | |
| 					if (oldEntry.size === 0) {
 | |
| 						this._deleteKey(key);
 | |
| 					}
 | |
| 					this._addInternal(newKey, item);
 | |
| 				}
 | |
| 			};
 | |
| 		} else {
 | |
| 			const oldEntry = /** @type {LazyBucketSortedSet<T, any>} */ (
 | |
| 				this._map.get(key)
 | |
| 			);
 | |
| 			const finishUpdate = oldEntry.startUpdate(item);
 | |
| 			return remove => {
 | |
| 				if (remove) {
 | |
| 					this.size--;
 | |
| 					finishUpdate(true);
 | |
| 					if (oldEntry.size === 0) {
 | |
| 						this._deleteKey(key);
 | |
| 					}
 | |
| 					return;
 | |
| 				}
 | |
| 				const newKey = this._getKey(item);
 | |
| 				if (key === newKey) {
 | |
| 					finishUpdate();
 | |
| 				} else {
 | |
| 					finishUpdate(true);
 | |
| 					if (oldEntry.size === 0) {
 | |
| 						this._deleteKey(key);
 | |
| 					}
 | |
| 					this._addInternal(newKey, item);
 | |
| 				}
 | |
| 			};
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {Iterator<T>[]} iterators list of iterators to append to
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	_appendIterators(iterators) {
 | |
| 		if (this._unsortedItems.size > 0)
 | |
| 			iterators.push(this._unsortedItems[Symbol.iterator]());
 | |
| 		for (const key of this._keys) {
 | |
| 			const entry = this._map.get(key);
 | |
| 			if (this._leaf) {
 | |
| 				const leafEntry = /** @type {SortableSet<T>} */ (entry);
 | |
| 				const iterator = leafEntry[Symbol.iterator]();
 | |
| 				iterators.push(iterator);
 | |
| 			} else {
 | |
| 				const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry);
 | |
| 				nodeEntry._appendIterators(iterators);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @returns {Iterator<T>} the iterator
 | |
| 	 */
 | |
| 	[Symbol.iterator]() {
 | |
| 		const iterators = [];
 | |
| 		this._appendIterators(iterators);
 | |
| 		iterators.reverse();
 | |
| 		let currentIterator = iterators.pop();
 | |
| 		return {
 | |
| 			next: () => {
 | |
| 				const res = currentIterator.next();
 | |
| 				if (res.done) {
 | |
| 					if (iterators.length === 0) return res;
 | |
| 					currentIterator = iterators.pop();
 | |
| 					return currentIterator.next();
 | |
| 				}
 | |
| 				return res;
 | |
| 			}
 | |
| 		};
 | |
| 	}
 | |
| }
 | |
| 
 | |
| module.exports = LazyBucketSortedSet;
 |