/*
* This file is part of LanternServer, licensed under the MIT License (MIT).
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the Software), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.lanternpowered.server.util.concurrent;
import static org.lanternpowered.server.util.Conditions.checkArrayRange;
import java.io.Serializable;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicIntegerArray;
import javax.annotation.Nullable;
public class AtomicShortArray implements Serializable {
private static final long serialVersionUID = 3434275139515033068L;
private static boolean even(int index) {
return (index & 0x1) == 0;
}
private static int key(short odd, short even) {
return odd << 16 | even & 0xffff;
}
private static short keyOdd(int key) {
return (short) ((key >> 16) & 0xffff);
}
private static short keyEven(int key) {
return (short) (key & 0xffff);
}
private final int length;
private final int backingArraySize;
private final AtomicIntegerArray backingArray;
/**
* Creates a new {@link AtomicShortArray} of the given length, with all
* elements initially zero.
*
* @param length The length of the array
*/
public AtomicShortArray(int length) {
this.length = length;
this.backingArraySize = (length & 1) + (length >> 1);
this.backingArray = new AtomicIntegerArray(this.backingArraySize);
}
/**
* Creates a new {@link AtomicShortArray} of the given length, with all
* elements initially zero.
*
* @param array The content and length that should be used
*/
public AtomicShortArray(short[] array) {
this.length = array.length;
this.backingArraySize = (this.length & 1) + (this.length >> 1);
final int[] array0 = new int[this.backingArraySize];
for (int i = 0; i < this.backingArraySize; i++) {
int j = i << 1;
int value = array[j];
if (++j < array.length) {
value |= array[j] << 16;
}
array0[i] = value;
}
this.backingArray = new AtomicIntegerArray(array0);
}
private int getPacked(int index) {
return this.backingArray.get(index >> 1);
}
/**
* Gets the length of the array.
*
* @return The length
*/
public final int length() {
return this.length;
}
/**
* Gets an element from the array at a given index.
*
* @param index The index
* @return The element
*/
public final short get(int index) {
checkArrayRange(index, this.length);
int packed = this.getPacked(index);
return even(index) ? keyEven(packed) : keyOdd(packed);
}
/**
* Sets an element in the array at a given index and returns the old value.
*
* @param index The index
* @param value The new value
* @return The old value
*/
public final int getAndSet(int index, short value) {
checkArrayRange(index, this.length);
boolean success = false;
short odd = 0;
short even = 0;
short oldValue = 0;
int backingIndex = index >> 1;
boolean evenIndex = even(index);
while (!success) {
int oldPacked = this.backingArray.get(backingIndex);
if (evenIndex) {
even = value;
oldValue = keyEven(oldPacked);
odd = keyOdd(oldPacked);
} else {
oldValue = keyOdd(oldPacked);
even = keyEven(oldPacked);
odd = value;
}
int newPacked = key(odd, even);
success = this.backingArray.compareAndSet(backingIndex, oldPacked, newPacked);
}
return oldValue;
}
/**
* Sets two elements in the array at once. The index must be even.
*
* @param index The index
* @param even The new value for the element at (index)
* @param odd The new value for the element at (index + 1)
*/
public final void set(int index, short even, short odd) {
checkArrayRange(index, this.length);
if ((index & 0x1) != 0) {
throw new IllegalArgumentException("When setting 2 elements at once, the index must be even!");
}
this.backingArray.set(index >> 1, key(odd, even));
}
/**
* Sets the element at the given index, but only if the previous value was the expected value.
*
* @param index The index
* @param expected The expected value
* @param newValue The new value
* @return True on success
*/
public final boolean compareAndSet(int index, short expected, short newValue) {
checkArrayRange(index, this.length);
boolean success = false;
short odd = 0;
short even = 0;
short oldValue = 0;
int backingIndex = index >> 1;
boolean evenIndex = even(index);
while (!success) {
int oldPacked = this.backingArray.get(backingIndex);
if (evenIndex) {
even = newValue;
oldValue = keyEven(oldPacked);
odd = keyOdd(oldPacked);
} else {
oldValue = keyOdd(oldPacked);
even = keyEven(oldPacked);
odd = newValue;
}
if (oldValue != expected) {
return false;
}
int newPacked = key(odd, even);
success = this.backingArray.compareAndSet(backingIndex, oldPacked, newPacked);
}
return true;
}
private short addAndGet(int index, short delta, boolean old) {
checkArrayRange(index, this.length);
boolean success = false;
short newValue = 0;
short oldValue = 0;
while (!success) {
oldValue = this.get(index);
newValue = (short) (oldValue + delta);
success = this.compareAndSet(index, oldValue, newValue);
}
return old ? oldValue : newValue;
}
/**
* Gets an array containing all the values in the array. The returned values are
* not guaranteed to be from the same time instant.
*
* If an array is provided and it is the correct length, then
* that array will be used as the destination array.
*
* @param array The provided array
* @return An array containing the values in the array
*/
public final short[] getArray(@Nullable short[] array) {
if (array == null || array.length != this.length) {
array = new short[this.length];
}
for (int i = 0; i < this.length; i++) {
int packed = this.getPacked(i);
array[i] = keyEven(packed);
if (++i < this.length) {
array[i] = keyOdd(packed);
}
}
return array;
}
/**
* Sets an element to the given value.
*
* @param index The index
* @param value The new value
*/
public final void set(int index, short value) {
this.getAndSet(index, value);
}
/**
* Sets an element to the given value, but the update may not happen immediately.
*
* @param index The index
* @param value The new value
*/
public final void lazySet(int index, short value) {
this.set(index, value);
}
/**
* Sets the element at the given index, but only if the previous value was the expected value. This may fail spuriously.
*
* @param index The index
* @param expected The expected value
* @param newValue The new value
* @return True on success
*/
public final boolean weakCompareAndSet(int index, short expected, short newValue) {
return this.compareAndSet(index, expected, newValue);
}
/**
* Atomically adds a delta to an element, and gets the new value.
*
* @param index The index
* @param delta The delta to add to the element
* @return The new value
*/
public final short addAndGet(int index, short delta) {
return this.addAndGet(index, delta, false);
}
/**
* Atomically adds a delta to an element, and gets the old value.
*
* @param index The index
* @param delta The delta to add to the element
* @return The old value
*/
public final short getAndAdd(int index, short delta) {
return this.addAndGet(index, delta, true);
}
/**
* Atomically increments an element and returns the old value.
*
* @param index The index
* @return The old value
*/
public final short getAndIncrement(int index) {
return this.getAndAdd(index, (short) 1);
}
/**
* Atomically decrements an element and returns the old value.
*
* @param index The index
* @return The old value
*/
public final short getAndDecrement(int index) {
return this.getAndAdd(index, (short) -1);
}
/**
* Atomically increments an element and returns the new value.
*
* @param index The index
* @return The new value
*/
public final short incrementAndGet(int index) {
return this.addAndGet(index, (short) 1);
}
/**
* Atomically decrements an element and returns the new value.
*
* @param index The index
* @return The new value
*/
public final short decrementAndGet(int index) {
return this.addAndGet(index, (short) -1);
}
/**
* Gets an array containing all the values in the array.
*
* The returned values are not guaranteed to be from the same time instant.
*
* @return The array
*/
public final short[] getArray() {
return this.getArray(null);
}
/**
* Returns a string representation of the array.
*
* The returned values are not guaranteed to be from the same time instant.
*
* @return The string representation
*/
@Override
public String toString() {
return Arrays.toString(this.getArray());
}
}