( baseContext: TreeContext, totalChildren: number, index: number, )
| 78 | } |
| 79 | |
| 80 | export function pushTreeContext( |
| 81 | baseContext: TreeContext, |
| 82 | totalChildren: number, |
| 83 | index: number, |
| 84 | ): TreeContext { |
| 85 | const baseIdWithLeadingBit = baseContext.id; |
| 86 | const baseOverflow = baseContext.overflow; |
| 87 | |
| 88 | // The leftmost 1 marks the end of the sequence, non-inclusive. It's not part |
| 89 | // of the id; we use it to account for leading 0s. |
| 90 | const baseLength = getBitLength(baseIdWithLeadingBit) - 1; |
| 91 | const baseId = baseIdWithLeadingBit & ~(1 << baseLength); |
| 92 | |
| 93 | const slot = index + 1; |
| 94 | const length = getBitLength(totalChildren) + baseLength; |
| 95 | |
| 96 | // 30 is the max length we can store without overflowing, taking into |
| 97 | // consideration the leading 1 we use to mark the end of the sequence. |
| 98 | if (length > 30) { |
| 99 | // We overflowed the bitwise-safe range. Fall back to slower algorithm. |
| 100 | // This branch assumes the length of the base id is greater than 5; it won't |
| 101 | // work for smaller ids, because you need 5 bits per character. |
| 102 | // |
| 103 | // We encode the id in multiple steps: first the base id, then the |
| 104 | // remaining digits. |
| 105 | // |
| 106 | // Each 5 bit sequence corresponds to a single base 32 character. So for |
| 107 | // example, if the current id is 23 bits long, we can convert 20 of those |
| 108 | // bits into a string of 4 characters, with 3 bits left over. |
| 109 | // |
| 110 | // First calculate how many bits in the base id represent a complete |
| 111 | // sequence of characters. |
| 112 | const numberOfOverflowBits = baseLength - (baseLength % 5); |
| 113 | |
| 114 | // Then create a bitmask that selects only those bits. |
| 115 | const newOverflowBits = (1 << numberOfOverflowBits) - 1; |
| 116 | |
| 117 | // Select the bits, and convert them to a base 32 string. |
| 118 | const newOverflow = (baseId & newOverflowBits).toString(32); |
| 119 | |
| 120 | // Now we can remove those bits from the base id. |
| 121 | const restOfBaseId = baseId >> numberOfOverflowBits; |
| 122 | const restOfBaseLength = baseLength - numberOfOverflowBits; |
| 123 | |
| 124 | // Finally, encode the rest of the bits using the normal algorithm. Because |
| 125 | // we made more room, this time it won't overflow. |
| 126 | const restOfLength = getBitLength(totalChildren) + restOfBaseLength; |
| 127 | const restOfNewBits = slot << restOfBaseLength; |
| 128 | const id = restOfNewBits | restOfBaseId; |
| 129 | const overflow = newOverflow + baseOverflow; |
| 130 | return { |
| 131 | id: (1 << restOfLength) | id, |
| 132 | overflow, |
| 133 | }; |
| 134 | } else { |
| 135 | // Normal path |
| 136 | const newBits = slot << baseLength; |
| 137 | const id = newBits | baseId; |
no test coverage detected