thread.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. type Runner<T = unknown> = ((taskUnit: T) => T | undefined) | undefined;
  2. type ThreadOptions = { priority: number };
  3. let isLooping = false;
  4. function getMapKeys<K extends number | string | symbol>(map: Map<K, unknown>) {
  5. return Array.from(map).map((it) => it[0]);
  6. }
  7. function workLoop() {
  8. isLooping = true;
  9. const prioritys = getMapKeys(Thread.threadMap).sort((a, b) => b - a);
  10. const curPriority = prioritys[0];
  11. const curPriorityArray = Thread.threadMap.get(curPriority);
  12. curPriorityArray.forEach((thread) => {
  13. thread.samePriorityLength = curPriorityArray.length;
  14. let taskUnit = thread.taskUnit;
  15. thread.start();
  16. while (taskUnit && !thread.shouldYield && !thread.isEmpty) {
  17. taskUnit = thread.taskUnit = thread.exec();
  18. }
  19. if (taskUnit === undefined) {
  20. thread.destroy();
  21. } else {
  22. thread.pause();
  23. }
  24. });
  25. if (Thread.isAllEmpty()) {
  26. isLooping = false;
  27. } else {
  28. nextTick(workLoop);
  29. }
  30. }
  31. let channel: MessageChannel;
  32. function nextTick(callback) {
  33. if (window.MessageChannel) {
  34. if (!channel) {
  35. channel = new MessageChannel();
  36. channel.port1.onmessage = callback;
  37. }
  38. channel.port2.postMessage("notify");
  39. } else {
  40. setTimeout(callback, 0);
  41. }
  42. }
  43. class Thread {
  44. static create(
  45. runner: Runner,
  46. taskUnit: unknown = undefined,
  47. options: ThreadOptions
  48. ) {
  49. const thread = new Thread(runner, taskUnit, options);
  50. Thread.threads.push(thread);
  51. const newThreadPriority = thread.options.priority;
  52. const curPriorityArray: Thread[] | undefined =
  53. Thread.threadMap.get(newThreadPriority);
  54. if (curPriorityArray) {
  55. curPriorityArray.push(thread);
  56. } else {
  57. Thread.threadMap.set(newThreadPriority, [thread]);
  58. }
  59. return thread;
  60. }
  61. static threads: Thread[] = [];
  62. static threadMap: Map<number, Thread[]> = new Map();
  63. static isAllEmpty() {
  64. return !Thread.threads.find((thread) => !thread.isEmpty);
  65. }
  66. static yieldInterval = 5;
  67. runner: Runner = undefined;
  68. taskUnit: unknown = undefined;
  69. options: ThreadOptions = {
  70. priority: 0,
  71. };
  72. status: "running" | "destroyed" | "paused" = "paused";
  73. samePriorityLength = 0;
  74. private startTime = 0;
  75. constructor(runner: Runner, taskUnit: unknown, options: ThreadOptions) {
  76. this.runner = runner;
  77. this.taskUnit = taskUnit;
  78. this.options = Object.assign(this.options, options);
  79. }
  80. start() {
  81. this.status = "running";
  82. this.startTime = Date.now();
  83. if (!isLooping) {
  84. workLoop();
  85. }
  86. }
  87. pause() {
  88. this.status = "paused";
  89. this.startTime = 0;
  90. }
  91. exec() {
  92. return this.runner(this.taskUnit);
  93. }
  94. destroy() {
  95. Thread.threads.splice(
  96. Thread.threads.findIndex((it) => it === this),
  97. 1
  98. );
  99. const curPriorityArray = Thread.threadMap.get(this.options.priority);
  100. curPriorityArray.splice(
  101. curPriorityArray.findIndex((it) => it === this),
  102. 1
  103. );
  104. if (curPriorityArray.length === 0) {
  105. Thread.threadMap.delete(this.options.priority);
  106. }
  107. this.runner = undefined;
  108. this.options = undefined;
  109. this.taskUnit = undefined;
  110. this.status = "destroyed";
  111. }
  112. get shouldYield() {
  113. return Date.now() > this.startTime + this.yieldInterval;
  114. }
  115. get isEmpty() {
  116. return this.taskUnit === undefined;
  117. }
  118. get yieldInterval() {
  119. return Thread.yieldInterval / this.samePriorityLength;
  120. }
  121. }
  122. export { Thread };