first commit

This commit is contained in:
宫欣海
2025-02-20 11:27:28 +08:00
commit 68090ca38d
91 changed files with 9915 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
/**
* @Author: Gongxh
* @Date: 2024-12-07
* @Description: 二叉堆(默认最小堆) 支持最大堆和最小堆
*/
export abstract class HeapNode {
public index: number;
public abstract lessThan(other: HeapNode): boolean;
}
export class BinaryHeap<T extends HeapNode> {
private _nodes: Array<T>;
private _size: number;
private _capacity: number;
constructor(capacity: number) {
this._size = 0;
this._capacity = capacity <= 0 ? 4 : capacity;
this._nodes = new Array<T>(this._capacity);
}
/**
* 清空
*/
public clear(): void {
this._size = 0;
}
/**
* 获取节点
* @param index 节点索引
*/
public get(index: number): T {
return this._nodes[index];
}
/**
* 获取顶部节点
*/
public top(): T {
return this._nodes[0];
}
/**
* 是否包含节点
* @param node 节点
*/
public contains(node: T): boolean {
return node.index >= 0 && node.index < this._size;
}
/**
* Push节点
* @param node 节点
*/
public push(node: T): void {
const size = ++this._size;
if (size > this._capacity) {
this._capacity = this._nodes.length *= 2;
}
this._sortUp(node, size - 1);
}
/**
* Pop节点
* @returns
*/
public pop(): T {
if (this._size == 0) {
return null;
}
const nodes = this._nodes;
const node = nodes[0];
node.index = -1;
nodes[0] = null;
const size = --this._size;
if (size > 0) {
const finalNode = nodes[size];
nodes[size] = null;
this._sortDown(finalNode, 0);
}
return node;
}
/**
* 移除节点
* @param node 要移除的节点
*/
public remove(node: T): void {
if (!this.contains(node)) {
return;
}
const size = --this._size;
const nodes = this._nodes;
const newNode = (nodes[node.index] = nodes[size]);
newNode.index = node.index;
nodes[size] = null;
this.update(newNode);
node.index = -1;
}
/**
* 更新节点
* @param node 要更新的节点
*/
public update(node: T): boolean {
if (!this.contains(node)) {
return false;
}
const index = node.index;
const nodes = this._nodes;
if (index > 0 && nodes[index].lessThan(nodes[this._parent(index)])) {
this._sortUp(nodes[index], index);
} else {
this._sortDown(nodes[index], index);
}
return true;
}
private _parent(index: number): number {
return (index - 1) >> 1;
}
public get count(): number {
return this._size;
}
public get empty(): boolean {
return this._size == 0;
}
private _sortUp(node: T, index: number): void {
let parentIndex = this._parent(index);
const nodes = this._nodes;
while (index > 0 && node.lessThan(nodes[parentIndex])) {
nodes[parentIndex].index = index;
nodes[index] = nodes[parentIndex];
index = parentIndex;
parentIndex = this._parent(parentIndex);
}
node.index = index;
nodes[index] = node;
}
private _sortDown(node: T, index: number): void {
let childIndex = (index << 1) + 1;
const nodes = this._nodes;
const size = this._size;
while (childIndex < size) {
let newParent = node;
// left
if (nodes[childIndex].lessThan(newParent)) {
newParent = nodes[childIndex];
}
// right
if (childIndex + 1 < size && nodes[childIndex + 1].lessThan(newParent)) {
++childIndex;
newParent = nodes[childIndex];
}
if (node == newParent) {
break;
}
// swap down
newParent.index = index;
nodes[index] = newParent;
index = childIndex;
childIndex = (childIndex << 1) + 1;
}
node.index = index;
nodes[index] = node;
}
}

View File

@@ -0,0 +1,301 @@
function defaultEquals<T>(a: T, b: T): boolean {
return a === b;
}
/** 单链表结结构节点 */
export class LinkedNode<T> {
public element: T;
public next: LinkedNode<T>; // 下一项元素的指针
constructor(element: T) {
this.element = element;
this.next = undefined;
}
}
/** 双向链表结结构节点 */
export class DoublyNode<T> extends LinkedNode<T> {
public prev: DoublyNode<T>; // 上一项元素的指针
public next: DoublyNode<T>; // 下一元素的指针(重新定义下一个元素的类型)
constructor(element: T) {
super(element);
this.prev = undefined;
}
}
/** 单向链表 */
export class LinkedList<T> {
protected _equalsFn: (a: T, b: T) => boolean;
protected _count: number;
protected _head: LinkedNode<T>;
/**
* create
* @param equalsFn 比较是否相等(支持自定义)
*/
constructor(equalsFn?: (a: T, b: T) => boolean) {
this._equalsFn = equalsFn || defaultEquals;
this._count = 0;
this._head = undefined;
}
/** 向链表尾部添加元素 */
public push(element: T): void {
const node = new LinkedNode<T>(element);
let current: LinkedNode<T>;
if (this._head === undefined) {
this._head = node;
} else {
current = this._head;
while (current.next !== undefined) {
current = current.next;
}
current.next = node;
}
this._count++;
}
/**
* 在链表的指定位置插入一个元素。
* @param element 要插入的元素。
* @param index 插入位置的索引从0开始计数。
* @returns 如果插入成功返回true否则返回false。
*/
public insert(element: T, index: number): boolean {
if (index >= 0 && index <= this._count) {
const node = new LinkedNode<T>(element);
if (index === 0) {
const current = this._head;
node.next = current;
this._head = node;
} else {
const previous = this.getElementAt(index - 1);
const current = previous.next;
node.next = current;
previous.next = node;
}
this._count++;
return true;
}
return false;
}
/**
* 获取链表中指定位置的元素,如果不存在返回 underfined
* @param index
*/
public getElementAt(index: number): LinkedNode<T> {
if (index >= 0 && index <= this._count) {
let node = this._head;
for (let i = 0; i < index && node !== undefined; i++) {
node = node.next;
}
return node;
}
return undefined;
}
/**
* 从链表中移除一个元素
* @param element
*/
public remove(element: T): T {
return this.removeAt(this.indexOf(element));
}
/**
* 从链表的特定位置移除一个元素
* @param index
*/
public removeAt(index: number): T {
if (index >= 0 && index < this._count) {
let current = this._head;
if (index === 0) {
this._head = current.next;
} else {
const previous = this.getElementAt(index - 1);
current = previous.next;
previous.next = current.next;
}
this._count--;
current.next = undefined;
return current.element;
}
return undefined;
}
/**
* 返回元素在链表中的索引,如果没有则返回-1
* @param element
*/
public indexOf(element: T): number {
let current = this._head;
for (let i = 0; i < this._count && current !== undefined; i++) {
if (this._equalsFn(element, current.element)) {
return i;
}
current = current.next;
}
return -1;
}
public clear(): void {
this._head = undefined;
this._count = 0;
}
public getHead(): LinkedNode<T> {
return this._head;
}
public isEmpty(): boolean {
return this.size() === 0;
}
public size(): number {
return this._count;
}
public toString(): string {
if (this._head === undefined) {
return "";
}
let objString = `${this._head.element}`;
let current = this._head.next;
for (let i = 0; i < this.size() && current !== undefined; i++) {
objString = `${objString},${current.element}`;
current = current.next;
}
return objString;
}
}
/** 双向链表 */
export class DoublyLinkedList<T> extends LinkedList<T> {
protected _head: DoublyNode<T>; // 重新定义 head 类型
protected _tail: DoublyNode<T>;
/**
* create
* @param equalsFn 比较是否相等(支持自定义)
*/
constructor(equalsFn?: (a: T, b: T) => boolean) {
super(equalsFn);
this._tail = undefined;
}
/**
* 向链表尾部添加元素
* @param element
*/
public push(element: T): void {
this.insert(element, this._count);
}
/**
* 向链表指定位置添加元素
* @param element
* @param index
*/
public insert(element: T, index: number): boolean {
if (index >= 0 && index <= this._count) {
const node = new DoublyNode<T>(element);
let current = this._head;
if (index === 0) {
if (this._head === undefined) {
this._head = node;
this._tail = node;
} else {
node.next = current;
current.prev = node;
this._head = node;
}
} else if (index === this._count) {
current = this._tail;
current.next = node;
node.prev = current;
this._tail = node;
} else {
const previous = this.getElementAt(index - 1);
current = previous.next;
node.next = current;
previous.next = node;
current.prev = node;
node.prev = previous;
}
this._count++;
return true;
}
return false;
}
/**
* 从链表的特定位置移除一个元素
* @param index
*/
public removeAt(index: number): T {
if (index >= 0 && index < this._count) {
let current = this._head;
if (index === 0) {
this._head = current.next;
if (this._count === 1) {
this._tail = undefined;
} else {
this._head.prev = undefined;
}
} else if (index === this._count - 1) {
current = this._tail;
this._tail = current.prev;
this._tail.next = undefined;
} else {
current = this.getElementAt(index);
const previous = current.prev;
previous.next = current.next;
current.next.prev = previous;
}
this._count--;
current.next = undefined;
current.prev = undefined;
return current.element;
}
return null;
}
/**
* 获取链表中指定位置的元素,如果不存在返回 null
* @param index
*/
public getElementAt(index: number): DoublyNode<T> {
if (index >= 0 && index <= this._count) {
if (index > this._count * 0.5) {
// 从后向前找
let node = this._tail;
for (let i = this._count - 1; i > index && node !== undefined; i--) {
node = node.prev;
}
return node;
} else {
// 从前向后找
let node = this._head;
for (let i = 0; i < index && node !== undefined; i++) {
node = node.next;
}
return node;
}
}
return undefined;
}
public getHead(): DoublyNode<T> {
return this._head;
}
public getTail(): DoublyNode<T> {
return this._tail;
}
public clear(): void {
this._head = undefined;
this._tail = undefined;
this._count = 0;
}
}

View File

@@ -0,0 +1,42 @@
import { DoublyLinkedList } from "./LinkedList";
export class Stack<T> {
private _items: DoublyLinkedList<T>;
constructor(equalsFn?: (a: T, b: T) => boolean) {
this._items = new DoublyLinkedList<T>(equalsFn);
}
public push(element: T): void {
this._items.push(element);
}
public pop(): T {
if (this.isEmpty()) {
return undefined;
}
return this._items.removeAt(this.size() - 1);
}
public peek(): T {
if (this.isEmpty()) {
return undefined;
}
return this._items.getTail().element;
}
public size(): number {
return this._items.size();
}
public isEmpty(): boolean {
return this._items.isEmpty();
}
public clear(): void {
this._items.clear();
}
public toString(): string {
return this._items.toString();
}
}

250
src/tool/MD5.ts Normal file
View File

@@ -0,0 +1,250 @@
// const base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
class Crypt {
// Bit-wise rotation left
public static rotl(n: number, b: number): number {
return (n << b) | (n >>> (32 - b));
}
// Bit-wise rotation right
public static rotr(n: number, b: number): number {
return (n << (32 - b)) | (n >>> b);
}
// Swap big-endian to little-endian and vice versa
public static endianNumber(n: number): any {
return (Crypt.rotl(n, 8) & 0x00ff00ff) | (Crypt.rotl(n, 24) & 0xff00ff00);
}
// Swap big-endian to little-endian and vice versa
public static endianArray(n: number[]): any {
for (let i = 0, l = n.length; i < l; i++) {
n[i] = Crypt.endianNumber(n[i]);
}
return n;
}
// Generate an array of any length of random bytes
public static randomBytes(n: number): number[] {
const bytes = [];
for (; n > 0; n--) {
bytes.push(Math.floor(Math.random() * 256));
}
return bytes;
}
// Convert a byte array to big-endian 32-bit words
public static bytesToWords(bytes: number[]): number[] {
const words: any[] = [];
for (let i = 0, b = 0, l = bytes.length; i < l; i++, b += 8) {
words[b >>> 5] |= bytes[i] << (24 - (b % 32));
}
return words;
}
// Convert big-endian 32-bit words to a byte array
public static wordsToBytes(words: number[]): number[] {
const bytes = [];
for (let b = 0, l = words.length * 32; b < l; b += 8) {
bytes.push((words[b >>> 5] >>> (24 - (b % 32))) & 0xff);
}
return bytes;
}
// Convert a byte array to a hex string
public static bytesToHex(bytes: number[]): string {
const hex = [];
for (let i = 0, l = bytes.length; i < l; i++) {
hex.push((bytes[i] >>> 4).toString(16));
hex.push((bytes[i] & 0xf).toString(16));
}
return hex.join("");
}
// Convert a hex string to a byte array
public static hexToBytes(hex: string): number[] {
const bytes = [];
for (let c = 0, l = hex.length; c < l; c += 2) {
bytes.push(parseInt(hex.substr(c, 2), 16));
}
return bytes;
}
}
// Convert a string to a byte array
function stringToBytes(str: string): number[] {
str = unescape(encodeURIComponent(str));
const bytes = [];
for (let i = 0, l = str.length; i < l; i++) {
bytes.push(str.charCodeAt(i) & 0xff);
}
return bytes;
}
function isFastBuffer(obj: any): boolean {
return !!obj.constructor && typeof obj.constructor.isBuffer === "function" && obj.constructor.isBuffer(obj);
}
// For Node v0.10 support. Remove this eventually.
function isSlowBuffer(obj: any): boolean {
return typeof obj.readFloatLE === "function" && typeof obj.slice === "function" && isBuffer(obj.slice(0, 0));
}
function isBuffer(obj: any): boolean {
return obj && (isFastBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer);
}
// The core
const md5Lib = function (message: string): number[] {
const bytes = stringToBytes(message);
const m = Crypt.bytesToWords(bytes),
l = bytes.length * 8;
let ml = m.length;
let a = 1732584193,
b = -271733879,
c = -1732584194,
d = 271733878;
// Swap endian
for (let i = 0; i < ml; i++) {
m[i] = (((m[i] << 8) | (m[i] >>> 24)) & 0x00ff00ff) | (((m[i] << 24) | (m[i] >>> 8)) & 0xff00ff00);
}
// Padding
m[l >>> 5] |= 0x80 << l % 32;
m[(((l + 64) >>> 9) << 4) + 14] = l;
// Method shortcuts
const FF = md5Lib._ff,
GG = md5Lib._gg,
HH = md5Lib._hh,
II = md5Lib._ii;
ml = m.length;
for (let i = 0; i < ml; i += 16) {
const aa = a,
bb = b,
cc = c,
dd = d;
a = FF(a, b, c, d, m[i + 0], 7, -680876936);
d = FF(d, a, b, c, m[i + 1], 12, -389564586);
c = FF(c, d, a, b, m[i + 2], 17, 606105819);
b = FF(b, c, d, a, m[i + 3], 22, -1044525330);
a = FF(a, b, c, d, m[i + 4], 7, -176418897);
d = FF(d, a, b, c, m[i + 5], 12, 1200080426);
c = FF(c, d, a, b, m[i + 6], 17, -1473231341);
b = FF(b, c, d, a, m[i + 7], 22, -45705983);
a = FF(a, b, c, d, m[i + 8], 7, 1770035416);
d = FF(d, a, b, c, m[i + 9], 12, -1958414417);
c = FF(c, d, a, b, m[i + 10], 17, -42063);
b = FF(b, c, d, a, m[i + 11], 22, -1990404162);
a = FF(a, b, c, d, m[i + 12], 7, 1804603682);
d = FF(d, a, b, c, m[i + 13], 12, -40341101);
c = FF(c, d, a, b, m[i + 14], 17, -1502002290);
b = FF(b, c, d, a, m[i + 15], 22, 1236535329);
a = GG(a, b, c, d, m[i + 1], 5, -165796510);
d = GG(d, a, b, c, m[i + 6], 9, -1069501632);
c = GG(c, d, a, b, m[i + 11], 14, 643717713);
b = GG(b, c, d, a, m[i + 0], 20, -373897302);
a = GG(a, b, c, d, m[i + 5], 5, -701558691);
d = GG(d, a, b, c, m[i + 10], 9, 38016083);
c = GG(c, d, a, b, m[i + 15], 14, -660478335);
b = GG(b, c, d, a, m[i + 4], 20, -405537848);
a = GG(a, b, c, d, m[i + 9], 5, 568446438);
d = GG(d, a, b, c, m[i + 14], 9, -1019803690);
c = GG(c, d, a, b, m[i + 3], 14, -187363961);
b = GG(b, c, d, a, m[i + 8], 20, 1163531501);
a = GG(a, b, c, d, m[i + 13], 5, -1444681467);
d = GG(d, a, b, c, m[i + 2], 9, -51403784);
c = GG(c, d, a, b, m[i + 7], 14, 1735328473);
b = GG(b, c, d, a, m[i + 12], 20, -1926607734);
a = HH(a, b, c, d, m[i + 5], 4, -378558);
d = HH(d, a, b, c, m[i + 8], 11, -2022574463);
c = HH(c, d, a, b, m[i + 11], 16, 1839030562);
b = HH(b, c, d, a, m[i + 14], 23, -35309556);
a = HH(a, b, c, d, m[i + 1], 4, -1530992060);
d = HH(d, a, b, c, m[i + 4], 11, 1272893353);
c = HH(c, d, a, b, m[i + 7], 16, -155497632);
b = HH(b, c, d, a, m[i + 10], 23, -1094730640);
a = HH(a, b, c, d, m[i + 13], 4, 681279174);
d = HH(d, a, b, c, m[i + 0], 11, -358537222);
c = HH(c, d, a, b, m[i + 3], 16, -722521979);
b = HH(b, c, d, a, m[i + 6], 23, 76029189);
a = HH(a, b, c, d, m[i + 9], 4, -640364487);
d = HH(d, a, b, c, m[i + 12], 11, -421815835);
c = HH(c, d, a, b, m[i + 15], 16, 530742520);
b = HH(b, c, d, a, m[i + 2], 23, -995338651);
a = II(a, b, c, d, m[i + 0], 6, -198630844);
d = II(d, a, b, c, m[i + 7], 10, 1126891415);
c = II(c, d, a, b, m[i + 14], 15, -1416354905);
b = II(b, c, d, a, m[i + 5], 21, -57434055);
a = II(a, b, c, d, m[i + 12], 6, 1700485571);
d = II(d, a, b, c, m[i + 3], 10, -1894986606);
c = II(c, d, a, b, m[i + 10], 15, -1051523);
b = II(b, c, d, a, m[i + 1], 21, -2054922799);
a = II(a, b, c, d, m[i + 8], 6, 1873313359);
d = II(d, a, b, c, m[i + 15], 10, -30611744);
c = II(c, d, a, b, m[i + 6], 15, -1560198380);
b = II(b, c, d, a, m[i + 13], 21, 1309151649);
a = II(a, b, c, d, m[i + 4], 6, -145523070);
d = II(d, a, b, c, m[i + 11], 10, -1120210379);
c = II(c, d, a, b, m[i + 2], 15, 718787259);
b = II(b, c, d, a, m[i + 9], 21, -343485551);
a = (a + aa) >>> 0;
b = (b + bb) >>> 0;
c = (c + cc) >>> 0;
d = (d + dd) >>> 0;
}
return Crypt.endianArray([a, b, c, d]);
};
// Auxiliary functions
// eslint-disable-next-line max-params
md5Lib._ff = function (a: any, b: any, c: any, d: any, x: any, s: any, t: any): any {
const n = a + ((b & c) | (~b & d)) + (x >>> 0) + t;
return ((n << s) | (n >>> (32 - s))) + b;
};
// eslint-disable-next-line max-params
md5Lib._gg = function (a: any, b: any, c: any, d: any, x: any, s: any, t: any): any {
const n = a + ((b & d) | (c & ~d)) + (x >>> 0) + t;
return ((n << s) | (n >>> (32 - s))) + b;
};
// eslint-disable-next-line max-params
md5Lib._hh = function (a: any, b: any, c: any, d: any, x: any, s: any, t: any): any {
const n = a + (b ^ c ^ d) + (x >>> 0) + t;
return ((n << s) | (n >>> (32 - s))) + b;
};
// eslint-disable-next-line max-params
md5Lib._ii = function (a: any, b: any, c: any, d: any, x: any, s: any, t: any): any {
const n = a + (c ^ (b | ~d)) + (x >>> 0) + t;
return ((n << s) | (n >>> (32 - s))) + b;
};
/**
* 对字符串执行md5处理
*
* @export
* @param {string} message 要处理的字符串
* @returns {string} md5
*/
export function md5(message: string): string {
if (message === undefined || message === null) {
throw new Error("Illegal argument " + message);
}
return Crypt.bytesToHex(Crypt.wordsToBytes(md5Lib(message)));
}

36
src/tool/Math.ts Normal file
View File

@@ -0,0 +1,36 @@
const MathMin = Math.min;
const MathMax = Math.max;
const MathFloor = Math.floor;
const MathRandom = Math.random;
export class MathTool {
public static clampf(value: number, min: number, max: number): number {
return MathMin(MathMax(value, min), max);
}
/** 随机 min 到 max之间的整数 (包含 min 和 max) */
public static rand(min: number, max: number): number {
return MathFloor(MathRandom() * (max - min + 1) + min);
}
/** 随机 min 到 max之间的浮点数 (包含 min 和 max) */
public static randRange(min: number, max: number): number {
return MathRandom() * (max - min) + min;
}
public static rad(angle: number): number {
return (angle * Math.PI) / 180;
}
public static deg(radian: number): number {
return (radian * 180) / Math.PI;
}
public static smooth(num1: number, num2: number, elapsedTime: number, responseTime: number): number {
let out: number = num1;
if (elapsedTime > 0) {
out = out + (num2 - num1) * (elapsedTime / (elapsedTime + responseTime));
}
return out;
}
}

7
src/tool/header.ts Normal file
View File

@@ -0,0 +1,7 @@
/**
* @Author: Gongxh
* @Date: 2024-12-14
* @Description: tools 导出
*/

View File

@@ -0,0 +1,13 @@
/**
* @Author: Gongxh
* @Date: 2024-12-12
* @Description: 对象帮助类
*/
export class ObjectHelper {
public static getObjectProp(obj: Record<string, any>, key: string): any {
if (obj.hasOwnProperty(key)) {
return obj[key];
}
return (obj[key] = Object.assign({}, obj[key]));
}
}

44
src/tool/log.ts Normal file
View File

@@ -0,0 +1,44 @@
/**
* @Author: Gongxh
* @Date: 2024-12-05
* @Description: log相关的api
*/
import { KUNPO_DEBUG } from "../global/header";
function log(...args: any[]) {
console.log("kunpo:", ...args);
}
/**
* 开启debug模式后 输出调试信息
* @param args
*/
function debug(...args: any[]): void {
KUNPO_DEBUG && console.log("kunpo:", ...args);
}
/**
* 信息性消息 某些浏览器中会带有小图标,但颜色通常与 log 相同
* @param args
*/
function info(...args: any[]): void {
console.info("kunpo:", ...args);
}
/**
* 警告信息 黄色背景,通常带有警告图标
* @param args
*/
function warn(...args: any[]): void {
console.warn("kunpo:", ...args);
}
/**
* 错误消息 红色背景,通常带有错误图标
* @param args
*/
function error(...args: any[]): void {
console.error("kunpo:", ...args);
}
export { debug, error, info, log, warn };

199
src/tool/timer/Timer.ts Normal file
View File

@@ -0,0 +1,199 @@
/**
* @Author: Gongxh
* @Date: 2024-12-07
* @Description: 定时器管理类
*/
import { BinaryHeap } from "../DataStruct/BinaryHeap";
import { TimerNode } from "./TimerNode";
import { TimerNodePool } from "./TimerNodePool";
export class Timer {
private _timerNodeOrder: number = 0;
/** 经过的时间 */
private _elapsedTime: number = 0;
private _pool: TimerNodePool;
private _heap: BinaryHeap<TimerNode>;
/** 暂停的计时器 */
private _pausedTimers: Map<number, TimerNode>;
/**
* 定时器数量
* @readonly
* @type {number}
*/
public get timerCount(): number {
return this._heap.count;
}
/**
* 定时器管理类
*
* @param {number} initTimerCapacity 初始定时器容量
* @memberof Timer
*/
public constructor(initTimerCapacity: number) {
this._heap = new BinaryHeap<TimerNode>(initTimerCapacity);
this._pool = new TimerNodePool(initTimerCapacity);
this._pausedTimers = new Map<number, TimerNode>();
}
/**
* 启动一个计时器
* @param { Function } callback 回调方法
* @param {number} interval 回调间隔 (秒)
* @param {number} [loop=0] 重复次数0回调一次1~n回调n次-1无限重复
* @returns {number} 返回计时器id
*/
public start(callback: () => void, interval: number, loop: number = 0): number {
const timerNode = this._getTimerNode(callback, interval, loop);
this._heap.push(timerNode);
return timerNode.id;
}
/**
* 删除指定计时器
*
* @param {number} timerId 定时器ID
* @memberof Timer
*/
public stop(timerId: number): void {
const timerNode = this._pool.get(timerId);
if (timerNode) {
if (timerNode.pause) {
this._pausedTimers.delete(timerId);
}
this._heap.remove(timerNode);
this._pool.recycle(timerId);
}
}
/**
* 暂停定时器
*
* @param {number} timerId 定时器ID
* @memberof Timer
*/
public pause(timerId: number): void {
const timerNode = this._pool.get(timerId);
if (timerNode) {
timerNode.pauseRemainTime = timerNode.expireTime - this._elapsedTime;
this._heap.remove(timerNode);
this._pausedTimers.set(timerId, timerNode);
}
}
/**
* 继续定时器
*
* @param {number} timerId 定时器ID
* @memberof Timer
*/
public resume(timerId: number): void {
const timerNode = this._pausedTimers.get(timerId);
if (timerNode) {
timerNode.pause = false;
timerNode.expireTime = this._elapsedTime + timerNode.pauseRemainTime;
this._pausedTimers.delete(timerId);
this._heap.push(timerNode);
}
}
// /**
// * 根据回调更新定时器
// *
// * @param {number} timerId 定时器ID
// * @param {number} interval 回调间隔
// * @param {number} loop 重复次数
// * @param {boolean} [resetTime=false] 是否更新下次回调时间(从当前时间开始计时)
// * @returns {boolean} 如果TimerID存在则返回true
// * @memberof Timer
// */
// public updateTimer(timerId: number, interval: number, loop: number, resetTime: boolean = false): boolean {
// const timerNode = this._pool.get(timerId);
// if (!timerNode) {
// return false;
// }
// timerNode.interval = interval;
// timerNode.loop = loop;
// if (resetTime) {
// timerNode.expireTime = this._elapsedTime + interval;
// }
// return this._heap.update(timerNode);
// }
/**
* 更新时钟
*
* @param {number} deltaTime 更新间隔
* @memberof Timer
*/
public update(deltaTime: number): void {
const elapsedTime = (this._elapsedTime += deltaTime);
const heap = this._heap;
let timerNode = heap.top();
while (timerNode && timerNode.expireTime <= elapsedTime) {
const callback = timerNode.callback;
if (timerNode.loop == 0) {
heap.pop();
this._recycle(timerNode);
} else if (timerNode.loop > 0) {
// 处理多次回调定时器
if (--timerNode.loop == 0) {
heap.pop();
this._recycle(timerNode);
} else {
// 更新下一次回调
timerNode.expireTime = timerNode.expireTime + timerNode.interval;
heap.update(timerNode);
}
} else {
// 无限次数回调
// 更新下一次回调
timerNode.expireTime = timerNode.expireTime + timerNode.interval;
heap.update(timerNode);
}
callback();
timerNode = heap.top();
}
}
/**
* 清空所有定时器
*
* @memberof Timer
*/
public clear(): void {
this._heap.clear();
this._pool.clear();
this._pausedTimers.clear();
this._timerNodeOrder = 0;
}
private _getTimerNode(callback: () => void, interval: number, loop: number): TimerNode {
const timerNode = this._pool.allocate();
timerNode.orderIndex = ++this._timerNodeOrder;
timerNode.callback = callback;
timerNode.interval = interval;
timerNode.expireTime = this._elapsedTime + interval;
timerNode.loop = loop;
timerNode.pause = false;
return timerNode;
}
private _recycle(timerNode: TimerNode): void {
this._pool.recycle(timerNode.id);
}
}

View File

@@ -0,0 +1,55 @@
/**
* @Author: Gongxh
* @Date: 2024-12-07
* @Description: 计时器节点
*/
import { HeapNode } from "../DataStruct/BinaryHeap";
export class TimerNode extends HeapNode {
/** 定时器ID */
public id: number;
/** 定时器添加索引同一时间回调根据OrderIndex排序 */
public orderIndex: number;
/** 定时间隔 */
public interval: number;
/** 回调时间点 */
public expireTime: number;
/** 重复次数 */
public loop: number = 0;
/** 定时回调 */
public callback: () => void;
/** 暂停时剩余时间 */
public pauseRemainTime: number;
/** 是否暂停 */
public pause: boolean;
/** * 是否被回收 */
public recycled: boolean;
constructor(id: number) {
super();
this.id = id;
}
/**
* 是否比其他定时节点小
* @param {HeapNode} other 其他定时节点
* @returns {boolean}
*/
public lessThan(other: HeapNode): boolean {
const otherTimerNode = other as TimerNode;
if (Math.abs(this.expireTime - otherTimerNode.expireTime) <= 1e-5) {
return this.orderIndex < otherTimerNode.orderIndex;
}
return this.expireTime < otherTimerNode.expireTime;
}
}

View File

@@ -0,0 +1,123 @@
/**
* @Author: Gongxh
* @Date: 2024-12-07
* @Description: 计时器节点回收池
*/
import { TimerNode } from "./TimerNode";
const TimerIdBit = 19;
const TimerCount = 1 << (32 - TimerIdBit);
const TimerVersionMask = (1 << TimerIdBit) - 1;
const TimerMaxVersion = TimerVersionMask;
export class TimerNodePool {
private _pool: Array<TimerNode> = new Array<TimerNode>();
private _freeIndices: Array<number> = new Array<number>();
/**
* 定时器池
* @param {number} capacity 初始容量
*/
public constructor(capacity: number) {
for (let i = 0; i < capacity; ++i) {
const timerNode = new TimerNode(i << TimerIdBit);
timerNode.recycled = true;
this._pool.push(timerNode);
this._freeIndices.push(i);
}
}
/**
* 分配定时器节点
* @returns {TimerNode} 定时器节点
*/
public allocate(): TimerNode {
let timerNode: TimerNode;
const pools = this._pool;
if (this._freeIndices.length == 0) {
if (pools.length == TimerCount) {
throw new Error("超出时钟个数: " + TimerCount);
}
timerNode = new TimerNode(pools.length << TimerIdBit);
pools.push(timerNode);
} else {
const index = this._freeIndices.pop();
timerNode = pools[index];
timerNode.recycled = false;
if ((timerNode.id & TimerVersionMask) == TimerMaxVersion) {
throw new Error("时钟版本号过高: " + TimerMaxVersion);
}
++timerNode.id;
}
return timerNode;
}
/**
* 回收定时器节点
* @param {number} timerId 定时器ID
*/
public recycle(timerId: number): void {
const index = timerId >>> TimerIdBit;
if (index < 0 || index >= this._pool.length) {
throw new Error("定时器不存在");
}
const timerNode = this._pool[index];
if (timerNode.recycled) {
throw new Error("定时器已经被回收");
}
timerNode.recycled = true;
timerNode.callback = null;
this._freeIndices.push(index);
}
/**
* 根据TimerID获取定时器节点
* @param {number} timerId 定时器ID
* @returns {TimerNode}
*/
public get(timerId: number): TimerNode | undefined {
const index = timerId >>> TimerIdBit;
const version = timerId & TimerVersionMask;
if (index < 0 || index >= this._pool.length) {
return null;
}
const timerNode = this._pool[index];
if (timerNode.recycled) {
return null;
}
const timerNodeVersion = timerNode.id & TimerVersionMask;
if (timerNodeVersion != version) {
return null;
}
return timerNode;
}
/**
* 清空正在使用的Timer
*/
public clear(): void {
const pools = this._pool;
const timerNodeCount = pools.length;
const freeIndices = this._freeIndices;
freeIndices.length = 0;
for (let i = 0; i < timerNodeCount; ++i) {
pools[i].recycled = true;
pools[i].callback = null;
freeIndices.push(i);
}
}
}