2
0

thread.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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. thread.pause();
  25. });
  26. if (Thread.isAllEmpty()) {
  27. isLooping = false;
  28. } else {
  29. nextTick(workLoop);
  30. }
  31. }
  32. let channel: MessageChannel;
  33. function nextTick(callback) {
  34. if (window.MessageChannel) {
  35. if (!channel) {
  36. channel = new MessageChannel();
  37. channel.port1.onmessage = callback;
  38. }
  39. channel.port2.postMessage("notify");
  40. } else {
  41. setTimeout(callback, 0);
  42. }
  43. }
  44. class Thread {
  45. static create(
  46. runner: Runner,
  47. taskUnit: unknown = undefined,
  48. options: ThreadOptions
  49. ) {
  50. const thread = new Thread(runner, taskUnit, options);
  51. Thread.threads.push(thread);
  52. const newThreadPriority = thread.options.priority;
  53. const curPriorityArray: Thread[] | undefined =
  54. Thread.threadMap.get(newThreadPriority);
  55. if (curPriorityArray) {
  56. curPriorityArray.push(thread);
  57. } else {
  58. Thread.threadMap.set(newThreadPriority, [thread]);
  59. }
  60. return thread;
  61. }
  62. static threads: Thread[] = [];
  63. static threadMap: Map<number, Thread[]> = new Map();
  64. static isAllEmpty() {
  65. return !Thread.threads.find((thread) => !thread.isEmpty);
  66. }
  67. static yieldInterval = 5;
  68. runner: Runner = undefined;
  69. taskUnit: unknown = undefined;
  70. options: ThreadOptions = {
  71. priority: 0,
  72. };
  73. status: "normal" | "destroyed" = "normal";
  74. samePriorityLength = 0;
  75. private startTime = 0;
  76. constructor(runner: Runner, taskUnit: unknown, options: ThreadOptions) {
  77. this.runner = runner;
  78. this.taskUnit = taskUnit;
  79. this.options = Object.assign(this.options, options);
  80. }
  81. start() {
  82. this.startTime = Date.now();
  83. if (!isLooping) {
  84. workLoop();
  85. }
  86. }
  87. pause() {
  88. this.startTime = 0;
  89. }
  90. exec() {
  91. return this.runner(this.taskUnit);
  92. }
  93. destroy() {
  94. Thread.threads.splice(
  95. Thread.threads.findIndex((it) => it === this),
  96. 1
  97. );
  98. const curPriorityArray = Thread.threadMap.get(this.options.priority);
  99. curPriorityArray.splice(
  100. curPriorityArray.findIndex((it) => it === this),
  101. 1
  102. );
  103. if (curPriorityArray.length === 0) {
  104. Thread.threadMap.delete(this.options.priority);
  105. }
  106. this.runner = undefined;
  107. this.options = undefined;
  108. this.taskUnit = undefined;
  109. }
  110. get shouldYield() {
  111. return Date.now() > this.startTime + this.yieldInterval;
  112. }
  113. get isEmpty() {
  114. return this.taskUnit === undefined;
  115. }
  116. get yieldInterval() {
  117. return Thread.yieldInterval / this.samePriorityLength;
  118. }
  119. }
  120. export { Thread };