@creejs/commons-collection 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.CommonsLang = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- function c(t){return !!h(t)&&t>0}function h(t){return null!=t&&"number"==typeof t}var L={assertPositive:q};function q(t,r){if(!c(t))throw new Error(`${r?'"'+r+'" ':" "}Not Positive: ${t}`)}new TextDecoder;new TextEncoder;
7
+ function a$1(t){return "boolean"==typeof t}function c$1(t){return "function"==typeof t}function l$1(t){return null!=t&&"object"==typeof t&&!A(t)}function y(t){return null==t}function p$1(t){return !!m$1(t)&&t>0}function h$1(t){return !!m$1(t)&&t>=0}function g$1(t){return !!m$1(t)&&t<0}function w$1(t){return null===t}function d$1(t){return void 0===t}function m$1(t){return null!=t&&"number"==typeof t}function b$1(t){return null!=t&&"object"==typeof t}function A(t){return null!==t&&"object"==typeof t&&(t.constructor===Object||void 0===t.constructor)}function E(t){return null!=t&&"function"==typeof t.then}function N(t){return null!=t&&"string"==typeof t}function $(t){return null!=t&&"symbol"==typeof t}function O$1(t){return ArrayBuffer.isView(t)&&t.constructor!==DataView}function v$1(t){return t instanceof Int8Array}function j(t){return t instanceof Uint8Array}function P(t){return t instanceof Uint8ClampedArray}function S(t){return t instanceof Int16Array}function x(t){return t instanceof Uint16Array}function U(t){return t instanceof Int32Array}function T(t){return t instanceof Uint32Array}function I(t){return t instanceof Float32Array}function B(t){return t instanceof Float64Array}function k$1(t){return t instanceof BigInt64Array}function L$1(t){return t instanceof BigUint64Array}function F(t){return t instanceof ArrayBuffer}var C={assertNumber:q,assertPositive:_$1,assertNegative:function(t,r){if(!g$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Negative: ${t}`)},assertNotNegative:J,assertBoolean:function(t,r){if(!a$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Boolean: type=${typeof t} value=${Z(t)}`)},assertObject:R,assertPlainObject:function(t,r){if(!A(t))throw new Error(`${r?'"'+r+'" ':""}Not PlainObject: type=${typeof t} value=${Z(t)}`)},assertSymbol:function(t,r){if(!$(t))throw new Error(`${r?'"'+r+'" ':""}Not Symbol: type=${typeof t} value=${Z(t)}`)},assertFunction:W,assertInstance:function(t,r){if(!l$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Class Instance: type=${typeof t} value=${Z(t)}`)},assertPromise:H,assertNil:function(t,r){if(!y(t))throw new Error(`${r?'"'+r+'" ':""}Neither Null nor Undefined: type=${typeof t} value=${Z(t)}`)},assertNotNil:V,assertNull:function(t,r){if(!w$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Null: type=${typeof t} value=${Z(t)}`)},assertNotNull:function(t,r){if(w$1(t))throw new Error((r?'"'+r+'" ':"")+"Should Not Null")},assertUndefined:function(t,r){if(!d$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Undefined: type=${typeof t} value=${Z(t)}`)},assertString:M,assertArray:D,assertStringOrSymbol:function(t,r){if(!N(t)&&!$(t))throw new Error(`${r?'"'+r+'" ':""}Not String or Symbol: type=${typeof t} value=${Z(t)}`)},assertInt8Array:function(t,r){if(v$1(t))throw new Error((r?'"'+r+'" ':"")+"Not Int8Array")},assertUint8Array:function(t,r){if(j(t))throw new Error((r?'"'+r+'" ':"")+"Not Uint8Array")},assertUint8ClampedArray:function(t,r){if(P(t))throw new Error((r?'"'+r+'" ':"")+"Not Uint8ClampedArray")},assertInt16Array:function(t,r){if(S(t))throw new Error((r?'"'+r+'" ':"")+"Not Int16Array")},assertUint16Array:function(t,r){if(x(t))throw new Error((r?'"'+r+'" ':"")+"Not Uint16Array")},assertInt32Array:function(t,r){if(U(t))throw new Error((r?'"'+r+'" ':"")+"Not Int32Array")},assertUint32Array:function(t,r){if(T(t))throw new Error((r?'"'+r+'" ':"")+"Not Uint32Array")},assertFloat32Array:function(t,r){if(I(t))throw new Error((r?'"'+r+'" ':"")+"Not Float32Array")},assertFloat64Array:function(t,r){if(B(t))throw new Error((r?'"'+r+'" ':"")+"Not Float64Array")},assertBigInt64Array:function(t,r){if(k$1(t))throw new Error((r?'"'+r+'" ':"")+"Not BigInt64Array")},assertBigUint64Array:function(t,r){if(L$1(t))throw new Error((r?'"'+r+'" ':"")+"Not BigUint64Array")},assertTypedArray:function(t,r){if(O$1(t))throw new Error((r?'"'+r+'" ':"")+"Not TypedArray")},assertArrayBuffer:z};function D(t,r){if(!Array.isArray(t))throw new Error(`${r?'"'+r+'" ':""}Not Array: type=${typeof t} value=${Z(t)}`)}function M(t,r){if(!N(t))throw new Error(`${r?'"'+r+'" ':""}Not String: type=${typeof t} value=${Z(t)}`)}function q(t,r){if(!m$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Number: type=${typeof t} value=${Z(t)}`)}function _$1(t,r){if(!p$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Positive: ${t}`)}function J(t,r){if(!h$1(t))throw new Error(`${r?'"'+r+'" ':""}Not "0 or Positive": ${t}`)}function R(t,r){if(!b$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Object: type=${typeof t} value=${Z(t)}`)}function W(t,r){if(!c$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Function: type=${typeof t} value=${Z(t)}`)}function H(t,r){if(!E(t))throw new Error(`${r?'"'+r+'" ':""}Not Promise: type=${typeof t} value=${Z(t)}`)}function V(t,r){if(y(t))throw new Error((r?'"'+r+'" ':"")+"Should Not Nil")}function z(t,r){if(!F(t))throw new Error((r?'"'+r+'" ':"")+"Not ArrayBuffer")}function Z(t){if(null===t)return "null";if(void 0===t)return "undefined";let r;try{r=JSON.stringify(t);}catch(e){r=t.toString();}return r}new TextDecoder;new TextEncoder;const dt=1e6;var mt={s2ns:1e9,ms2ns:dt,timestamp:function(){if("undefined"!=typeof performance&&"number"==typeof performance.timeOrigin){const t=performance.timeOrigin,r=performance.now();return Math.ceil((t+r)/dt)}return Date.now()},timestamp64:bt,lapseNano:At,lapseMillis:Et,timeoutNano:function(t,r){return At(t)>r},timeoutMillis:function(t,r){return Et(t)>r}};function bt(){if("undefined"!=typeof performance&&"number"==typeof performance.timeOrigin){const t=performance.timeOrigin,r=performance.now();return BigInt((t+r)*dt)}return BigInt(Date.now()*dt)}function At(t,r){return (r??bt())-t}function Et(t,r){r=r??bt();return BigInt(r-t)/BigInt(dt)}
8
8
 
9
9
  // internal
10
10
  // owned
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  // module vars
21
- const { assertPositive } = L;
21
+ const { assertPositive } = C;
22
22
 
23
23
  /**
24
24
  * A set that has a fixed capacity and automatically removes the oldest element when the capacity is reached.
@@ -36,9 +36,12 @@
36
36
  assertPositive(capacity, 'capacity');
37
37
  this.capacity = capacity;
38
38
  /**
39
+ * 1. key is the Value stored in CappedSet
40
+ * 2. value is a Node
41
+ * 3. JS Map preserve the insertion order
39
42
  * @type {Map<any, Node>}
40
43
  */
41
- this._set = new Map();
44
+ this._map = new Map();
42
45
  /**
43
46
  * @type {Node|undefined}
44
47
  */
@@ -50,21 +53,25 @@
50
53
  }
51
54
 
52
55
  get size () {
53
- return this._set.size
56
+ return this._map.size
54
57
  }
55
58
 
56
- // 获取最旧的元素
59
+ /**
60
+ * get the oldest element
61
+ */
57
62
  get oldest () {
58
63
  return this._head?.value
59
64
  }
60
65
 
61
- // 获取最新的元素
66
+ /**
67
+ * get the newest element
68
+ */
62
69
  get newest () {
63
70
  return this._tail?.value
64
71
  }
65
72
 
66
73
  [Symbol.iterator] () {
67
- return this._set.keys()
74
+ return this._map.keys()
68
75
  }
69
76
 
70
77
  /**
@@ -73,8 +80,8 @@
73
80
  * @param {*} value - The value to add to the set
74
81
  */
75
82
  add (value) {
76
- if (this._set.has(value)) {
77
- const node = this._set.get(value);
83
+ if (this._map.has(value)) {
84
+ const node = this._map.get(value);
78
85
  node && this._removeNode(node);
79
86
  } else if (this.size >= this.capacity) {
80
87
  this._removeOldest();
@@ -88,7 +95,7 @@
88
95
  * @returns {boolean} True if the value exists, false otherwise.
89
96
  */
90
97
  has (value) {
91
- return this._set.has(value)
98
+ return this._map.has(value)
92
99
  }
93
100
 
94
101
  /**
@@ -97,8 +104,8 @@
97
104
  * @returns {boolean} True if the value was found and removed, false otherwise.
98
105
  */
99
106
  delete (value) {
100
- if (this._set.has(value)) {
101
- const node = this._set.get(value);
107
+ if (this._map.has(value)) {
108
+ const node = this._map.get(value);
102
109
  node && this._removeNode(node);
103
110
  return true
104
111
  }
@@ -106,7 +113,7 @@
106
113
  }
107
114
 
108
115
  clear () {
109
- this._set.clear();
116
+ this._map.clear();
110
117
  this._head = undefined;
111
118
  this._tail = undefined;
112
119
  }
@@ -116,7 +123,7 @@
116
123
  * @returns {Iterator<any>} An iterator object that yields the values of the set.
117
124
  */
118
125
  values () {
119
- return this._set.keys()
126
+ return this._map.keys()
120
127
  }
121
128
 
122
129
  /**
@@ -138,7 +145,7 @@
138
145
  }
139
146
 
140
147
  this._tail = node;
141
- this._set.set(value, node);
148
+ this._map.set(value, node);
142
149
  }
143
150
 
144
151
  /**
@@ -160,7 +167,7 @@
160
167
  this._tail = node.prev;
161
168
  }
162
169
 
163
- this._set.delete(node.value);
170
+ this._map.delete(node.value);
164
171
  }
165
172
 
166
173
  _removeOldest () {
@@ -170,9 +177,543 @@
170
177
  }
171
178
  }
172
179
 
173
- var index = { CappedSet };
180
+ var e={isFunction:t,isNil:s};function t(e){return "function"==typeof e}function s(e){return null==e}function n(e){return null!=e&&"string"==typeof e}var r={assertNumber:function(e,t){if(!function(e){return null!=e&&"number"==typeof e}(e))throw new Error(`${t?'"'+t+'" ':""}Not Number: type=${typeof e} value=${i(e)}`)},assertFunction:function(e,s){if(!t(e))throw new Error(`${s?'"'+s+'" ':""}Not Function: type=${typeof e} value=${i(e)}`)},assertNotNil:function(e,t){if(s(e))throw new Error((t?'"'+t+'" ':"")+"Should Not Nil")},assertString:function(e,t){if(!n(e))throw new Error(`${t?'"'+t+'" ':""}Not String: type=${typeof e} value=${i(e)}`)},assertStringOrSymbol:function(e,t){if(!n(e)&&!function(e){return null!=e&&"symbol"==typeof e}(e))throw new Error(`${t?'"'+t+'" ':""}Not String or Symbol: type=${typeof e} value=${i(e)}`)}};function i(e){if(null===e)return "null";if(void 0===e)return "undefined";let t;try{t=JSON.stringify(e);}catch(s){t=e.toString();}return t}new TextDecoder,new TextEncoder;const l="DOwner$#$",{assertFunction:a,assertNotNil:o}=r;class c{constructor(e,t,s=false){o(e,"event"),a(t,"callback"),this._event=e,this._callback=t,this._isOnce=!!s,this._owner=void 0;}set owner(e){this._owner=e;}get owner(){return this._owner===l?void 0:this._owner}get event(){return this._event}get isOnce(){return this._isOnce}isSameCallback(e){return this._callback===e}get callback(){return this._callback}invoke(...e){try{return this._callback(...e)}finally{if(this._isOnce)try{this._event._remove(this);}catch(e){console.warn(e);}}}listener(...e){return this.invoke(...e)}}const{isFunction:h,isNil:u}=e,{assertStringOrSymbol:_,assertFunction:f}=r;class m{static get DefaultOwner(){return l}constructor(e){_(e,"eventName"),this._name=e,this._callbacks=new Set,this._listeners=[],this._callback2Listeners=new Map,this._listener2Owner=new Map,this._owner2Listeners=new Map;}get name(){return this._name}isEmpty(){return 0===this._callbacks.size}rawListeners(){return [...this._listeners]}listenerCount(e){return null==e?this._listeners.length:this._callback2Listeners.get(e)?.size??0}callbacks(){return [...this.rawListeners().map(e=>e.callback)]}emit(...e){if(0===this._listeners.length)return false;for(const t of [...this._listeners])t.invoke(...e);return true}emitOwner(e,...t){if(0===this._listeners.length)return false;const s=this._owner2Listeners.get(e);if(null==s)return false;for(const e of [...s])e.invoke(...t);return true}hasListener(e){return !!h(e)&&this._callbacks.has(e)}hasOwner(e){return !u(e)&&this._owner2Listeners.has(e)}addListener(e,t){return this._addListener(e,t,false,false)}prependListener(e,t){return this._addListener(e,t,false,true)}addOnceListener(e,t){return this._addListener(e,t,true,false)}prependOnceListener(e,t){return this._addListener(e,t,true,true)}_addListener(e,t,s,n){if(u(e))return false;f(e),this._callbacks.has(e)||this._callbacks.add(e),t=t??l;const r=new c(this,e,s);r.owner=t,n?this._listeners.unshift(r):this._listeners.push(r),this._listener2Owner.set(r,t);let i=this._callback2Listeners.get(e);null==i&&(i=new Set,this._callback2Listeners.set(e,i)),i.add(r);let a=this._owner2Listeners.get(t);return null==a&&(a=new Set,this._owner2Listeners.set(t,a)),a.add(r),true}removeListener(e){if(u(e))return false;if(!this._callbacks.has(e))return false;this._callbacks.delete(e);const t=this._callback2Listeners.get(e);if(null==t)return false;this._callback2Listeners.delete(e);for(const e of t){ -1!==this._listeners.indexOf(e)&&this._listeners.splice(this._listeners.indexOf(e),1);const t=this._listener2Owner.get(e);if(null==t)continue;this._listener2Owner.delete(e);const s=this._owner2Listeners.get(t);null!=s&&(s.delete(e),0===s.size&&this._owner2Listeners.delete(t));}return true}_remove(e){const t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1);const{callback:s}=e,n=this._callback2Listeners.get(s);null!=n&&(n.delete(e),0===n.size&&(this._callback2Listeners.delete(s),this._callbacks.delete(s)));const r=this._listener2Owner.get(e);if(null==r)return;this._listener2Owner.delete(e);const i=this._owner2Listeners.get(r);null!=i&&(i.delete(e),0===i.size&&this._owner2Listeners.delete(r));}removeAllListeners(e){if(u(e))return this._callbacks.clear(),this._listeners.length=0,this._callback2Listeners.clear(),this._listener2Owner.clear(),this._owner2Listeners.clear(),this;const t=this._owner2Listeners.get(e);if(null==t)return this;this._owner2Listeners.delete(e);for(const e of t){ -1!==this._listeners.indexOf(e)&&this._listeners.splice(this._listeners.indexOf(e),1),this._listener2Owner.delete(e);const{callback:t}=e,s=this._callback2Listeners.get(t);null!=s&&(s.delete(e),0===s.size&&(this._callback2Listeners.delete(t),this._callbacks.delete(t)));}return this}}const{isNil:d}=e,{assertString:w,assertFunction:L,assertNumber:v,assertStringOrSymbol:g,assertNotNil:p}=r,b=["on","once","addListener","prependListener","prependOnceListener","off","offAll","offOwner","removeAllListeners","removeListener","emit","emitOwner","setMaxListeners","getMaxListeners","hasOwner","listeners","listenerCount","eventNames","rawListeners"];let O=10;class k{static mixin(e){const t=new k;e.__emitter=t;for(const s of b){const n=t[s];e[s]=n.bind(t);}return e}static get defaultMaxListeners(){return O}static set defaultMaxListeners(e){v(e),O=e??10;}constructor(){this._name2Event=new Map,this._maxListeners=O;}addListener(e,t,s){return this.on(e,t,s)}prependListener(e,t,s){w(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).prependListener(t,s),this}prependOnceListener(e,t,s){w(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).prependOnceListener(t,s),this}emit(e,...t){const s=this._name2Event.get(e);return null!=s&&!s.isEmpty()&&(s.emit(...t),true)}emitOwner(e,t,...s){if(null==t)throw new Error('Missing "owner"');const n=this._name2Event.get(e);return null!=n&&!n.isEmpty()&&(n.emitOwner(t,...s),true)}eventNames(){return [...this._name2Event.keys()]}getMaxListeners(){return this._maxListeners}listenerCount(e,t){g(e,"eventName");const s=this._name2Event.get(e);return null==s||s.isEmpty()?0:s.listenerCount(t)}listeners(e){g(e,"eventName");const t=this._name2Event.get(e);return null==t||t.isEmpty()?[]:t.callbacks()}off(e,t){const s=this._name2Event.get(e);return null==s?this:(s.removeListener(t),s.isEmpty()?(this._name2Event.delete(e),this):this)}offAll(e,t){g(e,"eventName");const s=this._name2Event.get(e);return null==s?this:(s.removeAllListeners(t),s.isEmpty()?(this._name2Event.delete(e),this):this)}offOwner(e){p(e,"owner");const t=[...this._name2Event.values()];for(const s of t)s.removeAllListeners(e),s.isEmpty()&&this._name2Event.delete(s.name);return this}on(e,t,s){w(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).addListener(t,s),this}_checkMaxListeners(e){let t=0;0!==this._maxListeners&&this._maxListeners!==1/0&&(t=this.listenerCount(e))>=this._maxListeners&&console.warn(`maxlistenersexceededwarning: Possible EventEmitter memory leak detected. ${t} ${e} listeners added to [${this}]. Use emitter.setMaxListeners() to increase limit`);}once(e,t,s){w(e),L(t);return this._getOrCreateEvent(e).addOnceListener(t,s),this}rawListeners(e){return this._name2Event.get(e)?.rawListeners()||[]}removeAllListeners(e,t){return this.offAll(e,t)}removeListener(e,t){return this.off(e,t)}setMaxListeners(e){if(v(e),e<0)throw new RangeError("maxListeners must >=0");return this._maxListeners=e,this}_getOrCreateEvent(e){if(this._name2Event.has(e))return this._name2Event.get(e);const t=new m(e);return this._name2Event.set(e,t),t}hasOwner(e){if(d(e))return false;for(const t of this._name2Event.values())if(t.hasOwner(e))return true;return false}}
181
+
182
+ // 3rd
183
+ // internal
184
+ // owned
185
+
186
+ /**
187
+ * @typedef {number} Timestamp
188
+ * @typedef {{
189
+ * tickInterval: number, // ms
190
+ * tickCount: number // ms
191
+ * }} TimeWheelCacheOptions
192
+ */
193
+
194
+ // module vars
195
+ const Event$1 = {
196
+ Advance: 'advance',
197
+ Expired: 'expired'
198
+ };
199
+
200
+ /**
201
+ * Not Found Better Implementation in npmjs.com, so do it by ourselves.
202
+ *
203
+ * Key Points:
204
+ * 1. Basic Atom Unit is "tick", 1 tick = 1 tickInterval
205
+ * 3. How many milliseconds does 1 Tick represent is defined by "tickInterval"
206
+ * 4. How many Ticks does the Wheel own is defined by "tickCount"
207
+ */
208
+ class TimeWheelCache extends k {
209
+ get Event () {
210
+ return Event$1
211
+ }
212
+
213
+ /**
214
+ * @param {TimeWheelCacheOptions} [options]
215
+ */
216
+ constructor (options) {
217
+ super();
218
+ this.options = options ?? {};
219
+ /**
220
+ * How many milliseconds does one Tick represent
221
+ */
222
+ this._tickInterval = this.options.tickInterval ?? 1000;
223
+ /**
224
+ * How many Ticks does the Wheel have
225
+ */
226
+ this._tickCount = this.options.tickCount ?? 60;
227
+ /**
228
+ * Slots, one Tick owns one Slot to store key and expire timestamp
229
+ * @type {Map<any, {value: any, slotIndex: number, expireTimestamp: number}>[]}
230
+ */
231
+ this._slots = Array(this._tickCount).fill(undefined).map(() => new Map());
232
+ /**
233
+ * Data Cache
234
+ * @type {Map<any, {value: any, slotIndex: number, expireTimestamp: number}>}
235
+ */
236
+ this._cache = new Map();
237
+ this._currentSlotIndex = 0;
238
+ /** @type {NodeJS.Timeout|undefined} */
239
+ this._timer = undefined;
240
+
241
+ // must start it immediately
242
+ this._startAutoEvict();
243
+ }
244
+
245
+ get tickInterval () {
246
+ return this._tickInterval
247
+ }
248
+
249
+ get tickCount () {
250
+ return this._tickCount
251
+ }
252
+
253
+ /**
254
+ * Max Time to Live, atom unit "millisecond"
255
+ * @returns {number}
256
+ */
257
+ get maxTtl () {
258
+ if (this._maxTtl == null) {
259
+ this._maxTtl = this._tickCount * this._tickInterval; // ms
260
+ }
261
+ return this._maxTtl
262
+ }
263
+
264
+ assertStarted () {
265
+ if (!this.autoEvictRunning) {
266
+ throw new Error(`${this.constructor.name} is not started`)
267
+ }
268
+ }
269
+
270
+ get autoEvictRunning () {
271
+ return this._timer != null
272
+ }
273
+
274
+ _startAutoEvict () {
275
+ this._timer = setInterval(() => this._advance(), this._tickInterval);
276
+ }
277
+
278
+ _stopAutoEvict () {
279
+ this._timer && clearInterval(this._timer);
280
+ this._timer = undefined;
281
+ }
282
+
283
+ _advance () {
284
+ const nextSlotIndex = (this._currentSlotIndex + 1) % this._tickCount;
285
+ const nextSlot = this._slots[nextSlotIndex];
286
+ const currentSlot = this._slots[this._currentSlotIndex];
287
+ this.emit(Event$1.Advance, this._currentSlotIndex, currentSlot, nextSlot);
288
+ if (currentSlot.size > 0) {
289
+ // clear current slot, and expired data
290
+ for (const key of currentSlot.keys()) {
291
+ const wrapped = this._cache.get(key);
292
+ this._cache.delete(key);
293
+ // @ts-ignore
294
+ this.emit(Event$1.Expired, key, wrapped.value, wrapped.expireTimestamp);
295
+ }
296
+ currentSlot.clear();
297
+ }
298
+ this._currentSlotIndex = nextSlotIndex;
299
+ }
300
+
301
+ start () {
302
+ if (this.autoEvictRunning) {
303
+ return
304
+ }
305
+ this._startAutoEvict();
306
+ }
307
+
308
+ stop () {
309
+ if (!this.autoEvictRunning) {
310
+ return
311
+ }
312
+ this._stopAutoEvict();
313
+ }
314
+
315
+ destroy () {
316
+ this._stopAutoEvict();
317
+ this.clear();
318
+ }
319
+
320
+ /**
321
+ * 1. "key" is not Nil
322
+ * 2. "value" is not Nil
323
+ * 3. "ttl" must > 0 && <= maxTtl, maxTtl = tickCount * tickInterval
324
+ * * > 0: CAN NOT go to past
325
+ * * < maxTtl: Wheel is Round, eg.
326
+ * * "Hour Wheel" has 24 Hour-Tick-Mark
327
+ * * CAN NOT distinguish 24 hours and 48 hours
328
+ * 4. Understand the difference between "ttl" and "timestamp"
329
+ * * TTL is Indexed from 1
330
+ * * eg. ttl = 60, should be stored in Slot[59]
331
+ * * Timestamp is Indexed from 0
332
+ * @param {any} key
333
+ * @param {any} value
334
+ * @param {number} ttl Time to Live, atom unit "millisecond"
335
+ * @returns {boolean}
336
+ */
337
+ set (key, value, ttl) {
338
+ this.assertStarted();
339
+ C.assertNotNil(key, 'key');
340
+ C.assertNotNil(value, 'value');
341
+ if (ttl <= 0) {
342
+ throw new Error(`Bad ttl "${ttl}", must > 0`)
343
+ }
344
+ if (ttl > this.maxTtl) {
345
+ throw new Error(`Bad ttl "${ttl}", must <= maxTtl(${this.maxTtl})`)
346
+ }
347
+ // delete existed firstly, Slot may be changed
348
+ if (this._cache.has(key)) {
349
+ this.delete(key);
350
+ }
351
+ // TimeUtils.timestamp() is built on Uptime, not affected by system time change
352
+ const expireTimestamp = mt.timestamp() + ttl;
353
+ // 1 tick at least
354
+ // ttl -1, eg. 1~60s should be stored in Slot[0]
355
+ const ticks = Math.floor((ttl - 1) / this._tickInterval);
356
+ const targetSlotIndex = (this._currentSlotIndex + ticks) % this._tickCount;
357
+ const storedItem = { value, slotIndex: targetSlotIndex, expireTimestamp };
358
+ this._slots[targetSlotIndex].set(key, storedItem);
359
+ this._cache.set(key, storedItem);
360
+ return true
361
+ }
362
+
363
+ /**
364
+ * @param {any} key
365
+ * @returns {boolean}
366
+ */
367
+ delete (key) {
368
+ const wrapped = this._cache.get(key);
369
+ if (!wrapped) {
370
+ return false
371
+ }
372
+ this._cache.delete(key);
373
+ // delete from target slot
374
+ // Time-Wheel may be Minute-Hour-Based, it's too long to leave it in slot
375
+ const { slotIndex } = wrapped;
376
+ const targetSlot = this._slots[slotIndex];
377
+ targetSlot.delete(key);
378
+ return true
379
+ }
380
+
381
+ /**
382
+ * Checks if the cache contains the specified key.
383
+ * @param {any} key - The key to check for existence in the cache.
384
+ * @returns {boolean} - True if the key exists in the cache, false otherwise.
385
+ */
386
+ has (key) {
387
+ const wrapped = this._cache.get(key);
388
+ if (!wrapped) {
389
+ return false
390
+ }
391
+ if (wrapped.expireTimestamp <= mt.timestamp()) {
392
+ this.delete(key);
393
+ this.emit('expired', key, wrapped.value, wrapped.expireTimestamp);
394
+ return false
395
+ }
396
+ return true
397
+ }
398
+
399
+ clear () {
400
+ this._cache.clear();
401
+ this._slots.forEach(slot => slot.clear());
402
+ this._currentSlotIndex = 0;
403
+ }
404
+
405
+ /**
406
+ *
407
+ * @param {any} key
408
+ * @returns {any}
409
+ */
410
+ get (key) {
411
+ const wrapped = this._cache.get(key);
412
+ if (!wrapped) {
413
+ return
414
+ }
415
+ if (wrapped.expireTimestamp <= mt.timestamp()) {
416
+ this.delete(key);
417
+ this.emit('expired', key, wrapped.value, wrapped.expireTimestamp);
418
+ return
419
+ }
420
+ return wrapped.value
421
+ }
422
+
423
+ size () {
424
+ // current slot may contain expired data
425
+ const slot = this._slots[this._currentSlotIndex];
426
+ if (slot.size > 0) {
427
+ const now = mt.timestamp();
428
+ for (const [key, { expireTimestamp }] of slot.entries()) {
429
+ if (expireTimestamp <= now) {
430
+ const wrapped = this._cache.get(key);
431
+ // @ts-ignore
432
+ this.emit('expired', key, wrapped.value, wrapped.expireTimestamp);
433
+ slot.delete(key);
434
+ this._cache.delete(key);
435
+ }
436
+ }
437
+ }
438
+ return this._cache.size
439
+ }
440
+ }
441
+
442
+ // 3rd
443
+ // internal
444
+ /**
445
+ * @typedef {number} Timestamp
446
+ */
447
+
448
+ // module vars
449
+ const SecondInMillisecond = 1_000; // 1s
450
+ const MinuteInMillisecond = 60_000; // 1m, 60s
451
+ const HourInMillisecond = 3_600_000; // 1h, 3600s
452
+ const Hour24InMillisecond = 86_400_000; // 24 hours, 86400s
453
+
454
+ const DowngradType = {
455
+ HourToSecond: 'hour->second',
456
+ HourToMinute: 'hour->minute',
457
+ MinuteToSecond: 'minute->second'
458
+ };
459
+
460
+ const Event = {
461
+ Downgrade: 'downgrade',
462
+ Expired: 'expired'
463
+ };
464
+
465
+ const MaxTtl = Hour24InMillisecond - 1;
466
+
467
+ /**
468
+ * Hour-Minute-Second-Time-Wheel Cache
469
+ * 1. TTL must be less than 24 Hours
470
+ */
471
+ class Hour24TimeWheelCache extends k {
472
+ static get DowngradType () {
473
+ return DowngradType
474
+ }
475
+
476
+ static get Event () {
477
+ return Event
478
+ }
479
+
480
+ /**
481
+ *
482
+ */
483
+ constructor () {
484
+ super();
485
+ /**
486
+ * Second Wheel:
487
+ * 1. 1 Tick Mark is 1 Second, is 1000 Milliseconds
488
+ * 2. 60 Slots, maximumly, SencondWheel can contain 60 Seconds
489
+ * 3. 60 Seconds should be stored in MinuteWheel
490
+ * * 01 00:00:00.XXX -> 00:00:59.XXX in Slot01 Index00
491
+ * * 02 00:00:01.XXX -> 00:00:01.XXX in Slot02 Index01
492
+ * * 60 00:00:59.XXX -> 00:00:59.XXX in Slot60 Index59
493
+ * @type {TimeWheelCache}
494
+ */
495
+ this._secondWheel = new TimeWheelCache({ tickInterval: SecondInMillisecond, tickCount: 60 });
496
+ /**
497
+ * Minute Wheel:
498
+ * 1. 1 Tick Mark is 1 Minute, is 60 * 1000 Milliseconds
499
+ * 2. 60 Slots, maximumly, MinuteWheel can contain 60 Minutes
500
+ * * 01 00:00:00 -> 00:00:59 in Slot01 Index00
501
+ * * 02 00:01:00 -> 00:01:59 in Slot02 Index01
502
+ * * 60 00:59:00 -> 00:59:59 in Slot60 Index59
503
+ * @type {TimeWheelCache}
504
+ */
505
+ this._minuteWheel = new TimeWheelCache({ tickInterval: MinuteInMillisecond, tickCount: 60 });
506
+ /**
507
+ * Hour Wheel:
508
+ * 1. 1 Tick Mark is 1 Hour, is 60 * 60 * 1000 Milliseconds
509
+ * 2. 24 Slots, maximumly, HourWheel can contain 23:59:59
510
+ * * 01 00:00:00 -> 00:59:59 in Slot01 Index00
511
+ * * 02 01:00:00 -> 01:59:59 in Slot02 Index01
512
+ * * 24 23:00:00 -> 23:59:59 in Slot23 Index23
513
+ * @type {TimeWheelCache}
514
+ */
515
+ this._hourWheel = new TimeWheelCache({ tickInterval: HourInMillisecond, tickCount: 24 });
516
+ /**
517
+ * @type {Map<any, TimeWheelCache>}
518
+ */
519
+ this._cache = new Map();
520
+ this._init();
521
+ }
522
+
523
+ /**
524
+ * Max Time to Live, atom unit "millisecond"
525
+ * @returns {number}
526
+ */
527
+ get maxTtl () {
528
+ return MaxTtl
529
+ }
530
+
531
+ get autoEvictRunning () {
532
+ return this._secondWheel.autoEvictRunning || this._minuteWheel.autoEvictRunning || this._hourWheel.autoEvictRunning
533
+ }
534
+
535
+ _init () {
536
+ /**
537
+ * 1. We don't store "Timetick: < 00:00:01 -> 00:59:59" into HourWheel, instead, we store them into MinuteWheel
538
+ * 2. When HourWeel advances, take Elements of next Slot, and downgrade them to MinuteWheel
539
+ */
540
+ this._hourWheel.on('advance', (/** @type {number} */currentSlotIndex,
541
+ /** @type {Map<any, {value:any, expireTimestamp: Timestamp}>} */currentSlot,
542
+ /** @type {Map<any, {value:any, expireTimestamp: Timestamp}>} */nextSlot) => {
543
+ for (const [key, { value, expireTimestamp }] of nextSlot) {
544
+ let leftTtl = expireTimestamp - mt.timestamp();
545
+ let downgradeType;
546
+ if (leftTtl <= 0) {
547
+ leftTtl = 1000;
548
+ downgradeType = DowngradType.HourToSecond;
549
+ this._secondWheel.set(key, value, leftTtl);
550
+ this.emit(Event.Downgrade, key, value, downgradeType, leftTtl);
551
+ } else if (leftTtl < MinuteInMillisecond) {
552
+ downgradeType = DowngradType.HourToSecond;
553
+ this._secondWheel.set(key, value, leftTtl);
554
+ this.emit(Event.Downgrade, key, value, downgradeType, leftTtl);
555
+ } else {
556
+ downgradeType = DowngradType.HourToMinute;
557
+ this._minuteWheel.set(key, value, leftTtl);
558
+ }
559
+ this._hourWheel.delete(key);
560
+ this.emit(Event.Downgrade, key, value, downgradeType, leftTtl);
561
+ }
562
+ });
563
+ /**
564
+ * 1. We don't store "Timetick: < 00:01:00 -> 00:01:59" into MinuteWheel, instead, we store them into SecondWheel
565
+ * 2. When MinuteWheel advances, take Elements of next Slot, and downgrade them to SecondWheel
566
+ */
567
+ this._minuteWheel.on('advance', (/** @type {number} */currentSlotIndex,
568
+ /** @type {Map<any, {value:any, expireTimestamp: Timestamp}>} */currentSlot,
569
+ /** @type {Map<any, {value:any, expireTimestamp: Timestamp}>} */nextSlot) => {
570
+ // console.log('advance', currentSlotIndex)
571
+ for (const [key, { value, expireTimestamp }] of nextSlot) {
572
+ let leftTtl = expireTimestamp - mt.timestamp();
573
+ if (leftTtl <= 0) {
574
+ leftTtl = 999; // ms
575
+ }
576
+ // console.log('downgrad', key, value, DowngradType.MinuteToSecond, leftTtl)
577
+ this._secondWheel.set(key, value, leftTtl);
578
+ this.emit(Event.Downgrade, key, value, DowngradType.MinuteToSecond, leftTtl);
579
+ this._minuteWheel.delete(key);
580
+ }
581
+ });
582
+
583
+ this._secondWheel.on('expired', (/** @type {any} */key, /** @type {any} */value, /** @type {Timestamp} */expireTimestamp) => {
584
+ this.emit(Event.Expired, key, value, expireTimestamp);
585
+ });
586
+ }
587
+
588
+ start () {
589
+ this._secondWheel.start();
590
+ this._minuteWheel.start();
591
+ this._hourWheel.start();
592
+ }
593
+
594
+ stop () {
595
+ this._secondWheel.stop();
596
+ this._minuteWheel.stop();
597
+ this._hourWheel.stop();
598
+ }
599
+
600
+ destroy () {
601
+ this._secondWheel.destroy();
602
+ this._minuteWheel.destroy();
603
+ this._hourWheel.destroy();
604
+ }
605
+
606
+ /**
607
+ * @param {any} key
608
+ * @param {any} value
609
+ * @param {number} ttl Time To Live, unit "millisencond", ttl should < 24 Hours
610
+ * @returns {boolean}
611
+ */
612
+ set (key, value, ttl) {
613
+ if (ttl <= 0) {
614
+ throw new Error(`Bad ttl "${ttl}", must > 0`)
615
+ }
616
+ /**
617
+ */
618
+ let wheel;
619
+ if (ttl < MinuteInMillisecond) { // Timetick: 00:00:01 -> 00:00:59
620
+ wheel = this._secondWheel;
621
+ } else if (ttl < HourInMillisecond) { // Timetick: < 00:00:01 -> 00:59:59
622
+ wheel = this._minuteWheel;
623
+ } else if (ttl < Hour24InMillisecond) { // Timetick: 01:00:00 -> 23:59:59
624
+ wheel = this._hourWheel;
625
+ } else {
626
+ throw new Error('"ttl" Should <= Millisencod Of 24 Hours')
627
+ }
628
+ wheel.set(key, value, ttl);
629
+ this._cache.set(key, wheel);
630
+ return true
631
+ }
632
+
633
+ /**
634
+ * @param {any} key
635
+ * @returns {boolean}
636
+ */
637
+ delete (key) {
638
+ const wheel = this._cache.get(key);
639
+ if (!wheel) {
640
+ return false
641
+ }
642
+ return wheel.delete(key)
643
+ }
644
+
645
+ /**
646
+ * Checks if the cache contains the specified key.
647
+ * @param {any} key - The key to check for existence in the cache.
648
+ * @returns {boolean} - True if the key exists in the cache, false otherwise.
649
+ */
650
+ has (key) {
651
+ const wheel = this._cache.get(key);
652
+ if (!wheel) { // No wheel, No Data
653
+ return false
654
+ }
655
+ if (wheel.has(key)) { // In Wheel, Has Data
656
+ return true
657
+ }
658
+ // current wheel is second-Wheel, Not existed or expired
659
+ if (wheel === this._secondWheel) {
660
+ return false
661
+ }
662
+ // Not In Wheel, It may be down-graded to lower wheel
663
+ if (wheel === this._hourWheel) { // current wheel is hour, check minute wheel
664
+ return this._minuteWheel.has(key)
665
+ } else if (wheel === this._minuteWheel) { // current wheel is minute, check second wheel
666
+ return this._secondWheel.has(key)
667
+ }
668
+ // It doesn't exist actually
669
+ return false
670
+ }
671
+
672
+ clear () {
673
+ this._secondWheel.clear();
674
+ this._minuteWheel.clear();
675
+ this._hourWheel.clear();
676
+ this._cache.clear();
677
+ }
678
+
679
+ /**
680
+ *
681
+ * @param {any} key
682
+ * @returns {any}
683
+ */
684
+ get (key) {
685
+ const wheel = this._cache.get(key);
686
+ if (!wheel) { // No wheel, No Data
687
+ return
688
+ }
689
+ const value = wheel.get(key);
690
+ if (value != null) { // In Wheel, Has Data
691
+ return value
692
+ }
693
+ // current wheel is second-Wheel, Not existed or expired
694
+ if (wheel === this._secondWheel) {
695
+ return
696
+ }
697
+ // Not In Wheel, It may be down-graded to lower wheel
698
+ if (wheel === this._hourWheel) { // current wheel is hour, check minute wheel
699
+ return this._minuteWheel.get(key)
700
+ } else if (wheel === this._minuteWheel) { // current wheel is minute, check second wheel
701
+ return this._secondWheel.get(key)
702
+ }
703
+ // It doesn't exist actually
704
+ return undefined
705
+ }
706
+
707
+ size () {
708
+ return this._hourWheel.size() + this._minuteWheel.size() + this._secondWheel.size()
709
+ }
710
+ }
711
+
712
+ var index = { CappedSet, TimeWheelCache, Hour24TimeWheelCache };
174
713
 
175
714
  exports.CappedSet = CappedSet;
715
+ exports.Hour24TimeWheelCache = Hour24TimeWheelCache;
716
+ exports.TimeWheelCache = TimeWheelCache;
176
717
  exports.default = index;
177
718
 
178
719
  Object.defineProperty(exports, '__esModule', { value: true });