main

square/leakcanary

Last updated at: 29/12/2023 09:39

LongScatterSet.kt

TLDR

This file implements the LongScatterSet class, which is a Kotlin adaptation of the com.carrotsearch.hppc.LongScatterSet class from the HPPC library. The LongScatterSet class is a hash set that stores long values.

Methods

elementSequence

Returns a sequence of all the elements in the set in the order they are stored in the internal array.

plusAssign

Adds a key to the set using the add method.

add

Adds a key to the set.

contains

Returns true if the set contains the specified key, false otherwise.

remove

Removes the specified key from the set.

release

Resets the set to its initial state by clearing all keys.

ensureCapacity

Ensures that the set can hold at least the specified number of keys without resizing the internal array.

size

Returns the number of keys in the set.

Classes

None

/*
 *  Copyright 2010-2013, Carrot Search s.c., Boznicza 11/56, Poznan, Poland
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package shark.internal.hppc

import java.util.Locale

/**
 * Code from com.carrotsearch.hppc.LongScatterSet copy pasted, inlined and converted to Kotlin.
 *
 * See https://github.com/carrotsearch/hppc .
 */
internal class LongScatterSet(expectedElements: Int = 4) {
  /** The hash array holding keys.  */
  private var keys: LongArray = longArrayOf()

  /**
   * The number of stored keys (assigned key slots), excluding the special
   * "empty" key, if any.
   *
   * @see .size
   * @see .hasEmptyKey
   */
  private var assigned = 0

  /**
   * Mask for slot scans in [.keys].
   */
  private var mask = 0

  /**
   * Expand (rehash) [.keys] when [.assigned] hits this value.
   */
  private var resizeAt = 0

  /**
   * Special treatment for the "empty slot" key marker.
   */
  private var hasEmptyKey = false

  /**
   * The load factor for [.keys].
   */
  private val loadFactor = 0.75

  init {
    ensureCapacity(expectedElements)
  }

  fun elementSequence(): Sequence<Long> {
    val max = mask + 1
    var slot = -1
    return generateSequence {
      if (slot < max) {
        var existing: Long
        slot++
        while (slot < max) {
          existing = keys[slot]
          if (existing != 0L) {
            return@generateSequence existing
          }
          slot++
        }
      }
      if (slot == max && hasEmptyKey) {
        slot++
        return@generateSequence 0L
      }
      return@generateSequence null
    }
  }

  private fun hashKey(key: Long): Int {
    return HPPC.mixPhi(key)
  }

  operator fun plusAssign(key: Long) {
    add(key)
  }

  fun add(key: Long): Boolean {
    if (key == 0L) {
      val added = !hasEmptyKey
      hasEmptyKey = true
      return added
    } else {
      val keys = this.keys
      val mask = this.mask
      var slot = hashKey(key) and mask

      var existing = keys[slot]
      while (existing != 0L) {
        if (existing == key) {
          return false
        }
        slot = slot + 1 and mask
        existing = keys[slot]
      }

      if (assigned == resizeAt) {
        allocateThenInsertThenRehash(slot, key)
      } else {
        keys[slot] = key
      }

      assigned++
      return true
    }
  }

  operator fun contains(key: Long): Boolean {
    if (key == 0L) {
      return hasEmptyKey
    } else {
      val keys = this.keys
      val mask = this.mask
      var slot = hashKey(key) and mask
      var existing = keys[slot]
      while (existing != 0L) {
        if (existing == key) {
          return true
        }
        slot = slot + 1 and mask
        existing = keys[slot]
      }
      return false
    }
  }

  fun remove(key: Long): Boolean {
    return if (key == 0L) {
      val hadEmptyKey = hasEmptyKey
      hasEmptyKey = false
      hadEmptyKey
    } else {
      val keys = this.keys
      val mask = this.mask
      var slot = hashKey(key) and mask
      var existing: Long = keys[slot]
      while (existing != 0L) {
        if (existing == key) {
          shiftConflictingKeys(slot)
          return true
        }
        slot = slot + 1 and mask
        existing = keys[slot]
      }
      false
    }
  }

  /**
   * Shift all the slot-conflicting keys allocated to (and including) `slot`.
   */
  private fun shiftConflictingKeys(inputGapSlot: Int) {
    var gapSlot = inputGapSlot
    val keys = keys
    val mask = mask
    // Perform shifts of conflicting keys to fill in the gap.
    var distance = 0
    while (true) {
      val slot = (gapSlot + (++distance)) and mask
      val existing = keys[slot]
      if (existing == 0L) {
        break
      }
      val idealSlot = hashKey(existing)
      val shift = (slot - idealSlot) and mask
      if (shift >= distance) {
        // Entry at this position was originally at or before the gap slot.
        // Move the conflict-shifted entry to the gap's position and repeat the procedure
        // for any entries to the right of the current position, treating it
        // as the new gap.
        keys[gapSlot] = existing
        gapSlot = slot
        distance = 0
      }
    }
    // Mark the last found gap slot without a conflict as empty.
    keys[gapSlot] = 0L
    assigned--
  }

  fun release() {
    assigned = 0
    hasEmptyKey = false
    allocateBuffers(HPPC.minBufferSize(4, loadFactor))
  }

  fun ensureCapacity(expectedElements: Int) {
    if (expectedElements > resizeAt) {
      val prevKeys = this.keys
      allocateBuffers(HPPC.minBufferSize(expectedElements, loadFactor))
      if (size() != 0) {
        rehash(prevKeys)
      }
    }
  }

  fun size(): Int {
    return assigned + if (hasEmptyKey) 1 else 0
  }

  private fun rehash(fromKeys: LongArray) {
    // Rehash all stored keys into the new buffers.
    val keys = this.keys
    val mask = this.mask
    var existing: Long
    var i = fromKeys.size - 1
    while (--i >= 0) {
      existing = fromKeys[i]
      if (existing != 0L) {
        var slot = hashKey(existing) and mask
        while (keys[slot] != 0L) {
          slot = slot + 1 and mask
        }
        keys[slot] = existing
      }
    }
  }

  /**
   * Allocate new internal buffers. This method attempts to allocate
   * and assign internal buffers atomically (either allocations succeed or not).
   */
  private fun allocateBuffers(arraySize: Int) {
    // Ensure no change is done if we hit an OOM.
    val prevKeys = this.keys
    try {
      val emptyElementSlot = 1
      this.keys = LongArray(arraySize + emptyElementSlot)
    } catch (e: OutOfMemoryError) {
      this.keys = prevKeys
      throw RuntimeException(
        String.format(
          Locale.ROOT,
          "Not enough memory to allocate buffers for rehashing: %d -> %d",
          size(),
          arraySize
        ), e
      )
    }

    this.resizeAt = HPPC.expandAtCount(arraySize, loadFactor)
    this.mask = arraySize - 1
  }

  private fun allocateThenInsertThenRehash(
    slot: Int,
    pendingKey: Long
  ) {
    // Try to allocate new buffers first. If we OOM, we leave in a consistent state.
    val prevKeys = this.keys
    allocateBuffers(HPPC.nextBufferSize(mask + 1, size(), loadFactor))

    // We have succeeded at allocating new data so insert the pending key/value at
    // the free slot in the old arrays before rehashing.
    prevKeys[slot] = pendingKey

    // Rehash old keys, including the pending key.
    rehash(prevKeys)
  }
}