/* * This is a TypeScript port of the original Java version, which was written by * Gil Tene as described in * https://github.com/HdrHistogram/HdrHistogram * and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ import { PackedArrayContext, MINIMUM_INITIAL_PACKED_ARRAY_CAPACITY } from "./PackedArrayContext"; const NUMBER_OF_SETS = 8; const { pow, floor } = Math; /** * A Packed array of signed 64 bit values, and supports {@link #get get()}, {@link #set set()}, * {@link #add add()} and {@link #increment increment()} operations on the logical contents of the array. * * An {@link PackedLongArray} Uses {@link PackedArrayContext} to track * the array's logical contents. Contexts may be switched when a context requires resizing * to complete logical array operations (get, set, add, increment). Contexts are * established and used within critical sections in order to facilitate concurrent * implementors. * */ export class PackedArray { private arrayContext: PackedArrayContext; constructor( virtualLength: number, initialPhysicalLength: number = MINIMUM_INITIAL_PACKED_ARRAY_CAPACITY ) { this.arrayContext = new PackedArrayContext( virtualLength, initialPhysicalLength ); } public setVirtualLength(newVirtualArrayLength: number) { if (newVirtualArrayLength < this.length()) { throw new Error( "Cannot set virtual length, as requested length " + newVirtualArrayLength + " is smaller than the current virtual length " + this.length() ); } const currentArrayContext = this.arrayContext; if ( currentArrayContext.isPacked && currentArrayContext.determineTopLevelShiftForVirtualLength( newVirtualArrayLength ) == currentArrayContext.getTopLevelShift() ) { // No changes to the array context contents is needed. Just change the virtual length. currentArrayContext.setVirtualLength(newVirtualArrayLength); return; } this.arrayContext = currentArrayContext.copyAndIncreaseSize( this.getPhysicalLength(), newVirtualArrayLength ); } /** * Get value at virtual index in the array * @param index the virtual array index * @return the array value at the virtual index given */ get(index: number) { let value = 0; for (let byteNum = 0; byteNum < NUMBER_OF_SETS; byteNum++) { let byteValueAtPackedIndex = 0; // Deal with unpacked context: if (!this.arrayContext.isPacked) { return this.arrayContext.getAtUnpackedIndex(index); } // Context is packed: const packedIndex = this.arrayContext.getPackedIndex( byteNum, index, false ); if (packedIndex < 0) { return value; } byteValueAtPackedIndex = this.arrayContext.getAtByteIndex(packedIndex) * pow(2, byteNum << 3); value += byteValueAtPackedIndex; } return value; } /** * Increment value at a virrual index in the array * @param index virtual index of value to increment */ public increment(index: number) { this.add(index, 1); } private safeGetPackedIndexgetPackedIndex( setNumber: number, virtualIndex: number ) { //do { //try { return this.arrayContext.getPackedIndex(setNumber, virtualIndex, true); /*} catch (ex) { if (ex instanceof ResizeError) { this.arrayContext.resizeArray(ex.newSize); } else { throw ex; } }*/ //} while (true); } /** * Add to a value at a virtual index in the array * @param index the virtual index of the value to be added to * @param value the value to add */ public add(index: number, value: number) { let remainingValueToAdd = value; for ( let byteNum = 0, byteShift = 0; byteNum < NUMBER_OF_SETS; byteNum++, byteShift += 8 ) { // Deal with unpacked context: if (!this.arrayContext.isPacked) { this.arrayContext.addAndGetAtUnpackedIndex(index, value); return; } // Context is packed: const packedIndex = this.safeGetPackedIndexgetPackedIndex(byteNum, index); const byteToAdd = remainingValueToAdd & 0xff; const afterAddByteValue = this.arrayContext.addAtByteIndex( packedIndex, byteToAdd ); // Reduce remaining value to add by amount just added: remainingValueToAdd -= byteToAdd; remainingValueToAdd = remainingValueToAdd / pow(2, 8); // Account for carry: remainingValueToAdd += floor(afterAddByteValue / pow(2, 8)); if (remainingValueToAdd == 0) { return; // nothing to add to higher magnitudes } } } /** * Set the value at a virtual index in the array * @param index the virtual index of the value to set * @param value the value to set */ set(index: number, value: number) { let bytesAlreadySet = 0; let valueForNextLevels = value; for (let byteNum = 0; byteNum < NUMBER_OF_SETS; byteNum++) { // Establish context within: critical section // Deal with unpacked context: if (!this.arrayContext.isPacked) { this.arrayContext.setAtUnpackedIndex(index, value); return; } // Context is packed: if (valueForNextLevels == 0) { // Special-case zeros to avoid inflating packed array for no reason const packedIndex = this.arrayContext.getPackedIndex( byteNum, index, false ); if (packedIndex < 0) { return; // no need to create entries for zero values if they don't already exist } } // Make sure byte is populated: const packedIndex = this.arrayContext.getPackedIndex( byteNum, index, true ); // Determine value to write, and prepare for next levels const byteToWrite = valueForNextLevels & 0xff; valueForNextLevels = floor(valueForNextLevels / pow(2, 8)); if (byteNum < bytesAlreadySet) { // We want to avoid writing to the same byte twice when not doing so for the // entire 64 bit value atomically, as doing so opens a race with e.g. concurrent // adders. So dobn't actually write the byte if has been written before. continue; } this.arrayContext.setAtByteIndex(packedIndex, byteToWrite); bytesAlreadySet++; } } /** * Get the current physical length (in longs) of the array's backing storage * @return the current physical length (in longs) of the array's current backing storage */ getPhysicalLength() { return this.arrayContext.physicalLength; } /** * Get the (virtual) length of the array * @return the (virtual) length of the array */ length() { return this.arrayContext.getVirtualLength(); } /** * Clear the array contents */ public clear() { this.arrayContext.clear(); } public toString() { let output = "PackedArray:\n"; output += this.arrayContext.toString(); return output; } }