LazySet.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const makeSerializable = require("./makeSerializable");
  7. /**
  8. * Merges every queued iterable directly into the concrete backing set.
  9. * @template T
  10. * @param {Set<T>} targetSet set where items should be added
  11. * @param {Set<Iterable<T>>} toMerge iterables to be merged
  12. * @returns {void}
  13. */
  14. const merge = (targetSet, toMerge) => {
  15. for (const set of toMerge) {
  16. for (const item of set) {
  17. targetSet.add(item);
  18. }
  19. }
  20. };
  21. /**
  22. * Flattens nested `LazySet` instances into a single collection of iterables
  23. * that can later be merged into the backing set.
  24. * @template T
  25. * @param {Set<Iterable<T>>} targetSet set where iterables should be added
  26. * @param {LazySet<T>[]} toDeepMerge lazy sets to be flattened
  27. * @returns {void}
  28. */
  29. const flatten = (targetSet, toDeepMerge) => {
  30. for (const set of toDeepMerge) {
  31. if (set._set.size > 0) targetSet.add(set._set);
  32. if (set._needMerge) {
  33. // `_needMerge` means at least one queue exists, not both
  34. if (set._toMerge !== undefined) {
  35. for (const mergedSet of set._toMerge) {
  36. targetSet.add(mergedSet);
  37. }
  38. }
  39. if (set._toDeepMerge !== undefined) {
  40. flatten(targetSet, set._toDeepMerge);
  41. }
  42. }
  43. }
  44. };
  45. /**
  46. * Defines the set iterator type used by this module.
  47. * @template T
  48. * @typedef {import("typescript-iterable").SetIterator<T>} SetIterator
  49. */
  50. /**
  51. * Like Set but with an addAll method to eventually add items from another iterable.
  52. * Access methods make sure that all delayed operations are executed.
  53. * Iteration methods deopts to normal Set performance until clear is called again (because of the chance of modifications during iteration).
  54. * @template T
  55. */
  56. class LazySet {
  57. /**
  58. * Seeds the set with an optional iterable while preparing internal queues for
  59. * deferred merges.
  60. * @param {Iterable<T>=} iterable init iterable
  61. */
  62. constructor(iterable) {
  63. /** @type {Set<T>} */
  64. this._set = new Set(iterable);
  65. // Both queues stay undefined until something is actually queued: most
  66. // lazy sets never merge, and a build creates thousands of them.
  67. /** @type {Set<Iterable<T>> | undefined} */
  68. this._toMerge = undefined;
  69. /** @type {LazySet<T>[] | undefined} */
  70. this._toDeepMerge = undefined;
  71. /** @type {boolean} */
  72. this._needMerge = false;
  73. /** @type {boolean} */
  74. this._deopt = false;
  75. }
  76. /**
  77. * Flattens any nested lazy sets that were queued for merging.
  78. */
  79. _flatten() {
  80. const toDeepMerge = this._toDeepMerge;
  81. if (toDeepMerge === undefined || toDeepMerge.length === 0) return;
  82. const toMerge = this._toMerge || (this._toMerge = new Set());
  83. flatten(toMerge, toDeepMerge);
  84. toDeepMerge.length = 0;
  85. }
  86. /**
  87. * Materializes all deferred additions into the backing set.
  88. */
  89. _merge() {
  90. this._flatten();
  91. const toMerge = this._toMerge;
  92. if (toMerge !== undefined) {
  93. merge(this._set, toMerge);
  94. toMerge.clear();
  95. }
  96. this._needMerge = false;
  97. }
  98. /**
  99. * Reports whether the set is empty without forcing a full merge.
  100. * @returns {boolean} true when no items have been stored or queued
  101. */
  102. _isEmpty() {
  103. if (this._set.size > 0) return false;
  104. if (this._toMerge !== undefined && this._toMerge.size > 0) return false;
  105. return this._toDeepMerge === undefined || this._toDeepMerge.length === 0;
  106. }
  107. /**
  108. * Returns the number of items after applying any deferred merges.
  109. * @returns {number} number of items in the set
  110. */
  111. get size() {
  112. if (this._needMerge) this._merge();
  113. return this._set.size;
  114. }
  115. /**
  116. * Adds a single item immediately to the concrete backing set.
  117. * @param {T} item an item
  118. * @returns {LazySet<T>} itself
  119. */
  120. add(item) {
  121. this._set.add(item);
  122. return this;
  123. }
  124. /**
  125. * Queues another iterable or lazy set for later merging so large bulk adds
  126. * can stay cheap until the set is read.
  127. * @param {Iterable<T> | LazySet<T>} iterable a immutable iterable or another immutable LazySet which will eventually be merged into the Set
  128. * @returns {LazySet<T>} itself
  129. */
  130. addAll(iterable) {
  131. if (this._deopt) {
  132. const _set = this._set;
  133. for (const item of iterable) {
  134. _set.add(item);
  135. }
  136. } else {
  137. if (iterable instanceof LazySet) {
  138. if (iterable._isEmpty()) return this;
  139. const toDeepMerge = this._toDeepMerge || (this._toDeepMerge = []);
  140. toDeepMerge.push(iterable);
  141. this._needMerge = true;
  142. if (toDeepMerge.length > 100000) {
  143. this._flatten();
  144. }
  145. } else {
  146. (this._toMerge || (this._toMerge = new Set())).add(iterable);
  147. this._needMerge = true;
  148. }
  149. const toMerge = this._toMerge;
  150. if (toMerge !== undefined && toMerge.size > 100000) this._merge();
  151. }
  152. return this;
  153. }
  154. /**
  155. * Removes all items and clears every deferred merge queue.
  156. */
  157. clear() {
  158. this._set.clear();
  159. if (this._toMerge !== undefined) this._toMerge.clear();
  160. if (this._toDeepMerge !== undefined) this._toDeepMerge.length = 0;
  161. this._needMerge = false;
  162. this._deopt = false;
  163. }
  164. /**
  165. * Deletes an item after first materializing any deferred additions that may
  166. * contain it.
  167. * @param {T} value an item
  168. * @returns {boolean} true, if the value was in the Set before
  169. */
  170. delete(value) {
  171. if (this._needMerge) this._merge();
  172. return this._set.delete(value);
  173. }
  174. /**
  175. * Returns the set's entry iterator and permanently switches future
  176. * operations to eager merge mode to preserve iterator correctness.
  177. * @returns {SetIterator<[T, T]>} entries
  178. */
  179. entries() {
  180. this._deopt = true;
  181. if (this._needMerge) this._merge();
  182. return this._set.entries();
  183. }
  184. /**
  185. * Iterates over every item after forcing pending merges and switching to
  186. * eager mode for correctness during iteration.
  187. * @template K
  188. * @param {(value: T, value2: T, set: Set<T>) => void} callbackFn function called for each entry
  189. * @param {K} thisArg this argument for the callbackFn
  190. * @returns {void}
  191. */
  192. forEach(callbackFn, thisArg) {
  193. this._deopt = true;
  194. if (this._needMerge) this._merge();
  195. // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-method-this-argument
  196. this._set.forEach(callbackFn, thisArg);
  197. }
  198. /**
  199. * Checks whether an item is present after applying any deferred merges.
  200. * @param {T} item an item
  201. * @returns {boolean} true, when the item is in the Set
  202. */
  203. has(item) {
  204. if (this._needMerge) this._merge();
  205. return this._set.has(item);
  206. }
  207. /**
  208. * Returns the key iterator, eagerly materializing pending merges first.
  209. * @returns {SetIterator<T>} keys
  210. */
  211. keys() {
  212. this._deopt = true;
  213. if (this._needMerge) this._merge();
  214. return this._set.keys();
  215. }
  216. /**
  217. * Returns the value iterator, eagerly materializing pending merges first.
  218. * @returns {SetIterator<T>} values
  219. */
  220. values() {
  221. this._deopt = true;
  222. if (this._needMerge) this._merge();
  223. return this._set.values();
  224. }
  225. /**
  226. * Returns the default iterator over values after forcing pending merges.
  227. * @returns {SetIterator<T>} iterable iterator
  228. */
  229. [Symbol.iterator]() {
  230. this._deopt = true;
  231. if (this._needMerge) this._merge();
  232. return this._set[Symbol.iterator]();
  233. }
  234. /* istanbul ignore next */
  235. get [Symbol.toStringTag]() {
  236. return "LazySet";
  237. }
  238. /**
  239. * Serializes the fully materialized set contents into webpack's object
  240. * serialization stream.
  241. * @param {import("../serialization/ObjectMiddleware").ObjectSerializerContext<(number | T)[]>} context context
  242. */
  243. serialize({ write }) {
  244. if (this._needMerge) this._merge();
  245. write(this._set.size);
  246. for (const item of this._set) write(item);
  247. }
  248. /**
  249. * Restores a `LazySet` from serialized item data.
  250. * @template T
  251. * @param {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<(number | T)[]>} context context
  252. * @returns {LazySet<T>} lazy set
  253. */
  254. static deserialize({ read }) {
  255. const count = /** @type {number} */ (read());
  256. /** @type {T[]} */
  257. const items = [];
  258. for (let i = 0; i < count; i++) {
  259. items.push(/** @type {T} */ (read()));
  260. }
  261. return new LazySet(items);
  262. }
  263. }
  264. makeSerializable(LazySet, "webpack/lib/util/LazySet");
  265. module.exports = LazySet;