Current section
Files
Jump to
Current section
Files
src/atomic_array.gleam
//// Atomic mutable arrays with these properties:
////
////
//// - Atomics are 64 bit integers.
//// - Atomics can be represented as either signed or unsigned.
//// - Atomics wrap around at overflow and underflow operations.
//// - All operations guarantee atomicity. No intermediate results can be seen.
//// The result of one mutation can only be the input to one following mutation.
//// - All atomic operations are mutually ordered. If atomic B is updated after
//// atomic A, then that is how it will appear to any concurrent readers. No one
//// can read the new value of B and then read the old value of A.
//// - Indexes into atomic arrays are zero-based. An atomic array of arity N
//// contains N atomics with index from 0 to N-1.
////
//// Be aware that JavaScript numbers (and so Gleam `Int`s) cannot represent
//// all 64bit ints, so the larger and smaller values that can be contained
//// within array will lose precision when converted to a Gleam int with
//// functions such as `get` and `to_list`.
import gleam/int
/// A mutable atomic array of 64bit ints, either signed or unsigned depending
/// on whether it was created with the `new_unsigned` or `new_signed` function.
///
pub type AtomicArray
/// Create a new signed 64bit int array.
pub fn new_signed(size size: Int) -> AtomicArray {
ffi_new_signed(int.max(size, 1))
}
/// Create a new unsigned 64bit int array.
pub fn new_unsigned(size size: Int) -> AtomicArray {
ffi_new_unsigned(int.max(size, 1))
}
/// Read an int from the array.
///
/// Returns an error if the index is out of bounds.
///
@external(erlang, "atomic_array_ffi", "get")
@external(javascript, "./atomic_array_ffi.mjs", "get")
pub fn get(array: AtomicArray, index: Int) -> Result(Int, Nil)
/// Add an amount to an int at the given index in the array.
///
/// Returns an error if the index is out of bounds.
///
/// Will overflow or underflow if the resulting value does not fit in the int
/// size for the array.
///
@external(erlang, "atomic_array_ffi", "add")
@external(javascript, "./atomic_array_ffi.mjs", "add")
pub fn add(array: AtomicArray, index: Int, amount: Int) -> Result(Nil, Nil)
/// Replace an int in the array with a new one.
///
/// Returns an error if the index is out of bounds.
///
@external(erlang, "atomic_array_ffi", "exchange")
@external(javascript, "./atomic_array_ffi.mjs", "exchange")
pub fn exchange(
in array: AtomicArray,
at index: Int,
replace_with value: Int,
) -> Result(Int, Nil)
/// Replace an int in the array with a new one, providing that the int in the
/// array has some expected value.
///
/// Returns an error if the index is out of bounds.
///
@external(erlang, "atomic_array_ffi", "compare_exchange")
@external(javascript, "./atomic_array_ffi.mjs", "compare_exchange")
pub fn compare_exchange(
in array: AtomicArray,
at index: Int,
expect expected: Int,
replace_with value: Int,
) -> Result(Nil, CompareError)
pub type CompareError {
ComparisonOutOfBounds
ComparisonFailed(actual: Int)
}
/// Get the number of ints in the array.
///
/// Atomic arrays cannot be grown or shunk, they always have the same number of
/// elements as they were created with.
///
@external(erlang, "atomic_array_ffi", "size")
@external(javascript, "./atomic_array_ffi.mjs", "size")
pub fn size(array: AtomicArray) -> Int
/// Set the int at the given index to a new value.
///
/// Returns an error if the index is out of bounds.
///
/// If the new value does not fit for the size of int that the array contains
/// then it will overflow on JavaScript, but an error will be returned on
/// Erlang. If you want to make a pull request to make this consistent then we
/// will accept your changes!
///
@external(erlang, "atomic_array_ffi", "set")
@external(javascript, "./atomic_array_ffi.mjs", "set")
pub fn set(array: AtomicArray, index: Int, value: Int) -> Result(Nil, Nil)
@external(erlang, "atomic_array_ffi", "new_signed")
@external(javascript, "./atomic_array_ffi.mjs", "new_signed")
fn ffi_new_signed(size size: Int) -> AtomicArray
@external(erlang, "atomic_array_ffi", "new_unsigned")
@external(javascript, "./atomic_array_ffi.mjs", "new_unsigned")
fn ffi_new_unsigned(size size: Int) -> AtomicArray
@external(erlang, "atomic_array_ffi", "get_or_panic")
@external(javascript, "./atomic_array_ffi.mjs", "get_or_panic")
fn get_or_panic(array: AtomicArray, index: Int) -> Int
/// Convert the array to a list of ints.
///
/// Note that this operation is not atomic, so if another thread mutates the
/// array while this function is running you may see inconsistent results.
///
pub fn to_list(array: AtomicArray) -> List(Int) {
array_to_list(array, size(array) - 1, [])
}
fn array_to_list(
array: AtomicArray,
index: Int,
accumulator: List(Int),
) -> List(Int) {
case index {
-1 -> accumulator
_ -> {
let element = get_or_panic(array, index)
array_to_list(array, index - 1, [element, ..accumulator])
}
}
}