chunked-array.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /**
  2. * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * from https://github.com/dsehnal/CIFTools.js
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. /**
  8. * A generic chunked array builder.
  9. *
  10. * When adding elements, the array grows by a specified number
  11. * of elements and no copying is done until ChunkedArray.compact
  12. * is called.
  13. */
  14. interface ChunkedArray<T, C extends 1 | 2 | 3 | 4 = 1> {
  15. ctor: { new (size: number): ArrayLike<T> },
  16. elementSize: C,
  17. growBy: number,
  18. allocatedSize: number,
  19. /** current size of the array */
  20. elementCount: number,
  21. currentSize: number,
  22. currentChunk: any,
  23. currentIndex: number,
  24. chunks: any[][]
  25. }
  26. namespace ChunkedArray {
  27. type Sizes = 1 | 2 | 3 | 4
  28. export function is(x: any): x is ChunkedArray<any> {
  29. return x.creator && x.chunkSize;
  30. }
  31. function allocateNext(array: ChunkedArray<any, any>) {
  32. let nextSize = array.growBy * array.elementSize;
  33. array.currentSize = nextSize;
  34. array.currentIndex = 0;
  35. array.currentChunk = new array.ctor(nextSize);
  36. array.allocatedSize += nextSize;
  37. array.chunks[array.chunks.length] = array.currentChunk;
  38. }
  39. export function add4<T>(array: ChunkedArray<T, 4>, x: T, y: T, z: T, w: T) {
  40. if (array.currentIndex >= array.currentSize) allocateNext(array);
  41. const c = array.currentChunk;
  42. const i = array.currentIndex;
  43. c[i] = x;
  44. c[i + 1] = y;
  45. c[i + 2] = z;
  46. c[i + 3] = w;
  47. array.currentIndex += 4;
  48. return array.elementCount++;
  49. }
  50. export function add3<T>(array: ChunkedArray<T, 3>, x: T, y: T, z: T) {
  51. if (array.currentIndex >= array.currentSize) allocateNext(array);
  52. const c = array.currentChunk;
  53. const i = array.currentIndex;
  54. c[i] = x;
  55. c[i + 1] = y;
  56. c[i + 2] = z;
  57. array.currentIndex += 3;
  58. return array.elementCount++;
  59. }
  60. export function add2<T>(array: ChunkedArray<T, 2>, x: T, y: T) {
  61. if (array.currentIndex >= array.currentSize) allocateNext(array);
  62. const c = array.currentChunk;
  63. const i = array.currentIndex;
  64. c[i] = x;
  65. c[i + 1] = y;
  66. array.currentIndex += 2;
  67. return array.elementCount++;
  68. }
  69. export function add<T>(array: ChunkedArray<T, 1>, x: T) {
  70. if (array.currentIndex >= array.currentSize) allocateNext(array);
  71. array.currentChunk[array.currentIndex] = x;
  72. array.currentIndex += 1;
  73. return array.elementCount++;
  74. }
  75. export function addRepeat<T>(array: ChunkedArray<T, 1>, n: number, x: T) {
  76. for (let i = 0; i < n; i++) {
  77. if (array.currentIndex >= array.currentSize) allocateNext(array);
  78. array.currentChunk[array.currentIndex++] = x;
  79. array.elementCount++;
  80. }
  81. return array.elementCount;
  82. }
  83. export function addMany<T>(array: ChunkedArray<T, any>, data: ArrayLike<T>) {
  84. const { elementSize } = array;
  85. for (let i = 0, _i = data.length; i < _i; i += elementSize) {
  86. if (array.currentIndex >= array.currentSize) allocateNext(array);
  87. const { currentChunk } = array;
  88. for (let j = 0; j < elementSize; j++) {
  89. currentChunk[array.currentIndex++] = data[i + j];
  90. }
  91. array.elementCount++;
  92. }
  93. return array.elementCount;
  94. }
  95. /** If doNotResizeSingleton = true and the data fit into a single chunk, do not resize it. */
  96. export function compact<T>(array: ChunkedArray<T, any>, doNotResizeSingleton = false): ArrayLike<T> {
  97. return _compact(array, doNotResizeSingleton);
  98. }
  99. export function _compact<T>(array: ChunkedArray<T, any>, doNotResizeSingleton: boolean): ArrayLike<T> {
  100. const { ctor, chunks, currentIndex } = array;
  101. if (!chunks.length) return new ctor(0);
  102. if (chunks.length === 1) {
  103. if (doNotResizeSingleton || currentIndex === array.allocatedSize) {
  104. return chunks[0];
  105. }
  106. }
  107. let size = 0;
  108. for (let i = 0, _i = chunks.length - 1; i < _i; i++) size += chunks[i].length;
  109. size += array.currentIndex;
  110. const ret = new ctor(size) as any;
  111. let offset = 0;
  112. if (ret.buffer) {
  113. for (let i = 0, _i = chunks.length - 1; i < _i; i++) {
  114. ret.set(chunks[i], offset);
  115. offset += chunks[i].length;
  116. }
  117. } else {
  118. for (let i = 0, _i = chunks.length - 1; i < _i; i++) {
  119. const chunk = chunks[i];
  120. for (let j = 0, _j = chunk.length; j < _j; j++) ret[offset + j] = chunk[j];
  121. offset += chunk.length;
  122. }
  123. }
  124. const lastChunk = chunks[chunks.length - 1];
  125. if (ret.buffer && currentIndex >= array.currentSize) {
  126. ret.set(lastChunk, offset);
  127. } else {
  128. for (let j = 0, _j = lastChunk.length; j < _j; j++) ret[offset + j] = lastChunk[j];
  129. }
  130. return ret;
  131. }
  132. /**
  133. * The size of the initial chunk is elementSize * initialCount.
  134. * Use the provided array as the initial chunk. The size of the array must be divisible by the elementSize.
  135. */
  136. export function create<T, C extends Sizes = 1>(ctor: { new (size: number): ArrayLike<T> }, elementSize: C, chunkSize: number, initialChunkOrCount?: number | ArrayLike<T>): ChunkedArray<T, C> {
  137. const ret: ChunkedArray<T, C> = {
  138. ctor,
  139. elementSize,
  140. growBy: Math.max(1, Math.ceil(chunkSize)),
  141. allocatedSize: 0,
  142. elementCount: 0,
  143. currentSize: 0,
  144. currentChunk: void 0,
  145. currentIndex: 0,
  146. chunks: []
  147. };
  148. if (typeof initialChunkOrCount === 'undefined') return ret;
  149. if (typeof initialChunkOrCount === 'number') {
  150. ret.currentChunk = new ctor(initialChunkOrCount * elementSize);
  151. ret.allocatedSize = initialChunkOrCount * elementSize;
  152. ret.currentSize = ret.currentChunk.length;
  153. ret.chunks[0] = ret.currentChunk;
  154. return ret;
  155. }
  156. const initialChunk = initialChunkOrCount;
  157. if (initialChunk.length % elementSize !== 0) throw new Error('initialChunk length must be a multiple of the element size.');
  158. ret.currentChunk = initialChunk;
  159. ret.allocatedSize = initialChunk.length;
  160. ret.currentSize = initialChunk.length;
  161. ret.chunks[0] = initialChunk as any;
  162. return ret;
  163. }
  164. }
  165. export { ChunkedArray };