|
| 1 | +import { AsyncComputedSignalImpl } from '../reactive-primitives/impl/async-computed-signal-impl'; |
| 2 | +import { StoreHandler } from '../reactive-primitives/impl/store'; |
| 3 | +import { assertFalse } from '../shared/error/assert'; |
| 4 | +import { isQrl } from '../shared/qrl/qrl-utils'; |
| 5 | +import type { Chore } from '../shared/scheduler'; |
| 6 | +import { |
| 7 | + ssrNodeDocumentPosition, |
| 8 | + vnode_documentPosition, |
| 9 | +} from '../shared/scheduler-document-position'; |
| 10 | +import { ChoreType } from '../shared/util-chore-type'; |
| 11 | +import type { ISsrNode } from '../ssr/ssr-types'; |
| 12 | +import { vnode_isVNode } from './vnode'; |
| 13 | + |
| 14 | +export class ChoreArray extends Array<Chore> { |
| 15 | + add(value: Chore): number { |
| 16 | + /// We need to ensure that the `queue` is sorted by priority. |
| 17 | + /// 1. Find a place where to insert into. |
| 18 | + const idx = sortedFindIndex(this, value); |
| 19 | + |
| 20 | + if (idx < 0) { |
| 21 | + /// 2. Insert the chore into the queue. |
| 22 | + this.splice(~idx, 0, value); |
| 23 | + return idx; |
| 24 | + } |
| 25 | + |
| 26 | + const existing = this[idx]; |
| 27 | + /** |
| 28 | + * When a derived signal is updated we need to run vnode_diff. However the signal can update |
| 29 | + * multiple times during component execution. For this reason it is necessary for us to update |
| 30 | + * the chore with the latest result of the signal. |
| 31 | + */ |
| 32 | + if (existing.$payload$ !== value.$payload$) { |
| 33 | + existing.$payload$ = value.$payload$; |
| 34 | + } |
| 35 | + return idx; |
| 36 | + } |
| 37 | + |
| 38 | + delete(value: Chore) { |
| 39 | + // const idx = this.sortedFindIndex(this, value); |
| 40 | + // if (idx >= 0) { |
| 41 | + // this.splice(idx, 1); |
| 42 | + // } |
| 43 | + // return idx; |
| 44 | + const idx = this.indexOf(value); |
| 45 | + if (idx >= 0) { |
| 46 | + this.splice(idx, 1); |
| 47 | + } |
| 48 | + return idx; |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +export function sortedFindIndex(sortedArray: Chore[], value: Chore): number { |
| 53 | + /// We need to ensure that the `queue` is sorted by priority. |
| 54 | + /// 1. Find a place where to insert into. |
| 55 | + let bottom = 0; |
| 56 | + let top = sortedArray.length; |
| 57 | + while (bottom < top) { |
| 58 | + const middle = bottom + ((top - bottom) >> 1); |
| 59 | + const midChore = sortedArray[middle]; |
| 60 | + const comp = choreComparator(value, midChore); |
| 61 | + if (comp < 0) { |
| 62 | + top = middle; |
| 63 | + } else if (comp > 0) { |
| 64 | + bottom = middle + 1; |
| 65 | + } else { |
| 66 | + // We already have the host in the queue. |
| 67 | + return middle; |
| 68 | + } |
| 69 | + } |
| 70 | + return ~bottom; |
| 71 | +} |
| 72 | + |
| 73 | +/** |
| 74 | + * Compares two chores to determine their execution order in the scheduler's queue. |
| 75 | + * |
| 76 | + * @param a - The first chore to compare |
| 77 | + * @param b - The second chore to compare |
| 78 | + * @returns A number indicating the relative order of the chores. A negative number means `a` runs |
| 79 | + * before `b`. |
| 80 | + */ |
| 81 | +export function choreComparator(a: Chore, b: Chore): number { |
| 82 | + const macroTypeDiff = (a.$type$ & ChoreType.MACRO) - (b.$type$ & ChoreType.MACRO); |
| 83 | + if (macroTypeDiff !== 0) { |
| 84 | + return macroTypeDiff; |
| 85 | + } |
| 86 | + |
| 87 | + const aHost = a.$host$; |
| 88 | + const bHost = b.$host$; |
| 89 | + |
| 90 | + if (aHost !== bHost && aHost !== null && bHost !== null) { |
| 91 | + if (vnode_isVNode(aHost) && vnode_isVNode(bHost)) { |
| 92 | + // we are running on the client. |
| 93 | + const hostDiff = vnode_documentPosition(aHost, bHost); |
| 94 | + if (hostDiff !== 0) { |
| 95 | + return hostDiff; |
| 96 | + } |
| 97 | + } else { |
| 98 | + assertFalse(vnode_isVNode(aHost), 'expected aHost to be SSRNode but it is a VNode'); |
| 99 | + assertFalse(vnode_isVNode(bHost), 'expected bHost to be SSRNode but it is a VNode'); |
| 100 | + const hostDiff = ssrNodeDocumentPosition(aHost as ISsrNode, bHost as ISsrNode); |
| 101 | + if (hostDiff !== 0) { |
| 102 | + return hostDiff; |
| 103 | + } |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + const microTypeDiff = (a.$type$ & ChoreType.MICRO) - (b.$type$ & ChoreType.MICRO); |
| 108 | + if (microTypeDiff !== 0) { |
| 109 | + return microTypeDiff; |
| 110 | + } |
| 111 | + // types are the same |
| 112 | + |
| 113 | + const idxDiff = toNumber(a.$idx$) - toNumber(b.$idx$); |
| 114 | + if (idxDiff !== 0) { |
| 115 | + return idxDiff; |
| 116 | + } |
| 117 | + |
| 118 | + // If the host is the same (or missing), and the type is the same, we need to compare the target. |
| 119 | + if (a.$target$ !== b.$target$) { |
| 120 | + if (isQrl(a.$target$) && isQrl(b.$target$) && a.$target$.$hash$ === b.$target$.$hash$) { |
| 121 | + return 0; |
| 122 | + } |
| 123 | + // 1 means that we are going to process chores as FIFO |
| 124 | + return 1; |
| 125 | + } |
| 126 | + |
| 127 | + // ensure that the effect chores are scheduled for the same target |
| 128 | + // TODO: can we do this better? |
| 129 | + if ( |
| 130 | + a.$type$ === ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS && |
| 131 | + b.$type$ === ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS && |
| 132 | + ((a.$target$ instanceof StoreHandler && b.$target$ instanceof StoreHandler) || |
| 133 | + (a.$target$ instanceof AsyncComputedSignalImpl && |
| 134 | + b.$target$ instanceof AsyncComputedSignalImpl)) && |
| 135 | + a.$payload$ !== b.$payload$ |
| 136 | + ) { |
| 137 | + return 1; |
| 138 | + } |
| 139 | + |
| 140 | + // The chores are the same and will run only once |
| 141 | + return 0; |
| 142 | +} |
| 143 | + |
| 144 | +function toNumber(value: number | string): number { |
| 145 | + return typeof value === 'number' ? value : -1; |
| 146 | +} |
0 commit comments