LazySet.js 6.9 KB

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