use std::io;
use js_sys::{Atomics, Int32Array, SharedArrayBuffer, Uint8Array};
use crate::backend::Storage;
use crate::error::{Error, Result};
pub struct SharedArrayBufferStorage {
buf: SharedArrayBuffer,
bytes: Uint8Array, words: Int32Array, size: u64,
}
impl SharedArrayBufferStorage {
pub fn new(size: u64) -> Result<Self> {
let size_u32 = valid_size(size)?;
Self::from_buffer(SharedArrayBuffer::new(size_u32), size)
}
pub fn wrap(buf: SharedArrayBuffer) -> Result<Self> {
let size = buf.byte_length() as u64;
valid_size(size)?;
Self::from_buffer(buf, size)
}
fn from_buffer(buf: SharedArrayBuffer, size: u64) -> Result<Self> {
let bytes = Uint8Array::new(&buf);
let words = Int32Array::new(&buf);
Ok(SharedArrayBufferStorage {
buf,
bytes,
words,
size,
})
}
pub fn buffer(&self) -> SharedArrayBuffer {
self.buf.clone()
}
fn check_range(&self, offset: u64, len: u64) -> Result<()> {
let in_range = offset.checked_add(len).is_some_and(|end| end <= self.size);
if !in_range {
return Err(Error::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"backend: offset out of range",
)));
}
Ok(())
}
fn word_index(&self, offset: u64) -> Result<u32> {
if !offset.is_multiple_of(4) || offset + 4 > self.size {
return Err(Error::Io(io::Error::new(
io::ErrorKind::InvalidInput,
"backend: misaligned or out-of-range atomic offset",
)));
}
Ok((offset / 4) as u32)
}
}
fn valid_size(size: u64) -> Result<u32> {
if size == 0 || !size.is_multiple_of(4) {
return Err(Error::Io(io::Error::new(
io::ErrorKind::InvalidInput,
"backend: SharedArrayBuffer size must be a positive multiple of 4",
)));
}
u32::try_from(size).map_err(|_| {
Error::Io(io::Error::new(
io::ErrorKind::InvalidInput,
"backend: size does not fit in a JavaScript SharedArrayBuffer length (u32)",
))
})
}
fn js_err(context: &str, v: wasm_bindgen::JsValue) -> Error {
Error::Io(io::Error::other(format!("backend: {context}: {v:?}")))
}
impl Storage for SharedArrayBufferStorage {
fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<()> {
if buf.is_empty() {
return Ok(());
}
self.check_range(offset, buf.len() as u64)?;
let start = offset as u32;
let end = start + buf.len() as u32;
self.bytes.subarray(start, end).copy_to(buf);
Ok(())
}
fn write_at(&self, buf: &[u8], offset: u64) -> Result<()> {
if buf.is_empty() {
return Ok(());
}
self.check_range(offset, buf.len() as u64)?;
let start = offset as u32;
let end = start + buf.len() as u32;
self.bytes.subarray(start, end).copy_from(buf);
Ok(())
}
fn size(&self) -> u64 {
self.size
}
fn close(self) -> Result<()> {
Ok(())
}
fn load_u32_at(&self, offset: u64) -> Result<u32> {
let idx = self.word_index(offset)?;
let v = Atomics::load(&self.words, idx).map_err(|e| js_err("Atomics.load", e))?;
Ok(v as u32)
}
fn store_u32_at(&self, offset: u64, value: u32) -> Result<()> {
let idx = self.word_index(offset)?;
Atomics::store(&self.words, idx, value as i32).map_err(|e| js_err("Atomics.store", e))?;
Atomics::notify(&self.words, idx).map_err(|e| js_err("Atomics.notify", e))?;
Ok(())
}
}
impl SharedArrayBufferStorage {
pub(crate) fn wait_async(
&self,
offset: u64,
old: u32,
timeout_ms: f64,
) -> Result<Option<js_sys::Promise>> {
let idx = self.word_index(offset)?;
let outcome = Atomics::wait_async_with_timeout(&self.words, idx, old as i32, timeout_ms)
.map_err(|e| js_err("Atomics.waitAsync", e))?;
let is_async = js_sys::Reflect::get(&outcome, &wasm_bindgen::JsValue::from_str("async"))
.map_err(|e| js_err("Atomics.waitAsync result .async", e))?
.as_bool()
.unwrap_or(false);
if !is_async {
return Ok(None);
}
let value = js_sys::Reflect::get(&outcome, &wasm_bindgen::JsValue::from_str("value"))
.map_err(|e| js_err("Atomics.waitAsync result .value", e))?;
Ok(Some(js_sys::Promise::from(value)))
}
}