use wasm_bindgen::prelude::*;
use super::archive::WasmArchive;
use super::file::{uint8_array_to_vec, vec_to_uint8_array};
#[wasm_bindgen]
pub struct WasmMemoryConfig {
chunk_size: usize,
max_buffer_size: usize,
low_memory_mode: bool,
}
#[wasm_bindgen]
impl WasmMemoryConfig {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self::default()
}
#[wasm_bindgen(setter, js_name = "chunkSize")]
pub fn set_chunk_size(&mut self, size: usize) {
self.chunk_size = size.max(1024); }
#[wasm_bindgen(getter, js_name = "chunkSize")]
pub fn chunk_size(&self) -> usize {
self.chunk_size
}
#[wasm_bindgen(setter, js_name = "maxBufferSize")]
pub fn set_max_buffer_size(&mut self, size: usize) {
self.max_buffer_size = size.max(1024 * 1024); }
#[wasm_bindgen(getter, js_name = "maxBufferSize")]
pub fn max_buffer_size(&self) -> usize {
self.max_buffer_size
}
#[wasm_bindgen(setter, js_name = "lowMemoryMode")]
pub fn set_low_memory_mode(&mut self, enabled: bool) {
self.low_memory_mode = enabled;
if enabled {
self.chunk_size = self.chunk_size.min(16 * 1024); self.max_buffer_size = self.max_buffer_size.min(8 * 1024 * 1024); }
}
#[wasm_bindgen(getter, js_name = "lowMemoryMode")]
pub fn low_memory_mode(&self) -> bool {
self.low_memory_mode
}
#[wasm_bindgen(js_name = "autoDetect")]
pub fn auto_detect() -> Self {
let mut config = Self::default();
if let Some(memory_mb) = get_available_memory_mb() {
if memory_mb < 256 {
config.set_low_memory_mode(true);
} else if memory_mb < 512 {
config.chunk_size = 32 * 1024;
config.max_buffer_size = 16 * 1024 * 1024;
}
}
config
}
}
impl Default for WasmMemoryConfig {
fn default() -> Self {
Self {
chunk_size: 64 * 1024, max_buffer_size: 64 * 1024 * 1024, low_memory_mode: false,
}
}
}
#[wasm_bindgen(js_name = "extractWithMemoryLimit")]
pub fn extract_with_memory_limit(
archive: &mut WasmArchive,
entry_name: &str,
config: &WasmMemoryConfig,
on_chunk: &js_sys::Function,
) -> Result<(), JsValue> {
let data = archive.extract_entry(entry_name)?;
let data_vec = uint8_array_to_vec(&data);
for chunk in data_vec.chunks(config.chunk_size) {
let chunk_array = vec_to_uint8_array(chunk);
on_chunk.call1(&JsValue::NULL, &chunk_array)?;
}
Ok(())
}
#[wasm_bindgen(js_name = "extractMultipleWithLimit")]
pub fn extract_multiple_with_limit(
archive: &mut WasmArchive,
entry_names: js_sys::Array,
config: &WasmMemoryConfig,
on_entry: &js_sys::Function,
) -> Result<(), JsValue> {
let mut total_extracted = 0usize;
for i in 0..entry_names.length() {
let name = entry_names
.get(i)
.as_string()
.ok_or_else(|| JsValue::from_str("Entry names must be strings"))?;
let data = archive.extract_entry(&name)?;
let data_len = data.length() as usize;
if total_extracted + data_len > config.max_buffer_size {
return Err(JsValue::from_str(&format!(
"Memory limit exceeded: {} bytes extracted, limit is {} bytes",
total_extracted + data_len,
config.max_buffer_size
)));
}
on_entry.call2(&JsValue::NULL, &JsValue::from_str(&name), &data)?;
total_extracted += data_len;
}
Ok(())
}
#[wasm_bindgen(js_name = "getMemoryStats")]
pub fn get_memory_stats() -> Result<JsValue, JsValue> {
let obj = js_sys::Object::new();
if let Some(window) = web_sys::window() {
if let Ok(performance) = js_sys::Reflect::get(&window, &"performance".into()) {
if let Ok(memory) = js_sys::Reflect::get(&performance, &"memory".into()) {
if !memory.is_undefined() {
if let Ok(used) = js_sys::Reflect::get(&memory, &"usedJSHeapSize".into()) {
js_sys::Reflect::set(&obj, &"usedHeapSize".into(), &used)?;
}
if let Ok(total) = js_sys::Reflect::get(&memory, &"totalJSHeapSize".into()) {
js_sys::Reflect::set(&obj, &"totalHeapSize".into(), &total)?;
}
if let Ok(limit) = js_sys::Reflect::get(&memory, &"jsHeapSizeLimit".into()) {
js_sys::Reflect::set(&obj, &"heapLimit".into(), &limit)?;
}
}
}
}
}
Ok(obj.into())
}
#[wasm_bindgen(js_name = "canExtractSafely")]
pub fn can_extract_safely(
archive: &WasmArchive,
entry_name: &str,
config: &WasmMemoryConfig,
) -> Result<bool, JsValue> {
let entry = archive.get_entry(entry_name)?;
if entry.is_undefined() {
return Err(JsValue::from_str(&format!(
"Entry not found: {}",
entry_name
)));
}
let size = js_sys::Reflect::get(&entry, &"size".into())?
.as_f64()
.unwrap_or(0.0) as usize;
let estimated_memory = size * 2 + 1024 * 1024;
Ok(estimated_memory <= config.max_buffer_size)
}
#[wasm_bindgen(js_name = "requestGC")]
pub fn request_gc() {
if let Some(window) = web_sys::window() {
if let Ok(gc) = js_sys::Reflect::get(&window, &"gc".into()) {
if let Ok(gc_fn) = gc.dyn_into::<js_sys::Function>() {
let _ = gc_fn.call0(&JsValue::NULL);
}
}
}
let _ = vec![0u8; 1024 * 1024]; }
fn get_available_memory_mb() -> Option<u32> {
let window = web_sys::window()?;
let performance = js_sys::Reflect::get(&window, &"performance".into()).ok()?;
let memory = js_sys::Reflect::get(&performance, &"memory".into()).ok()?;
if memory.is_undefined() {
return None;
}
let limit = js_sys::Reflect::get(&memory, &"jsHeapSizeLimit".into()).ok()?;
let used = js_sys::Reflect::get(&memory, &"usedJSHeapSize".into()).ok()?;
let limit_bytes = limit.as_f64()?;
let used_bytes = used.as_f64()?;
let available = (limit_bytes - used_bytes) / (1024.0 * 1024.0);
Some(available as u32)
}
#[wasm_bindgen]
pub struct EntryIterator {
entries: js_sys::Array,
index: u32,
}
#[wasm_bindgen]
impl EntryIterator {
#[wasm_bindgen(constructor)]
pub fn new(archive: &WasmArchive) -> Result<EntryIterator, JsValue> {
let entries = archive.get_entries()?;
Ok(Self { entries, index: 0 })
}
#[wasm_bindgen(js_name = "hasNext")]
pub fn has_next(&self) -> bool {
self.index < self.entries.length()
}
pub fn next(&mut self) -> JsValue {
if self.index >= self.entries.length() {
return JsValue::UNDEFINED;
}
let entry = self.entries.get(self.index);
self.index += 1;
entry
}
pub fn reset(&mut self) {
self.index = 0;
}
#[wasm_bindgen(getter)]
pub fn count(&self) -> u32 {
self.entries.length()
}
#[wasm_bindgen(getter)]
pub fn position(&self) -> u32 {
self.index
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wasm_memory_config_default() {
let config = WasmMemoryConfig::default();
assert_eq!(config.chunk_size, 64 * 1024);
assert_eq!(config.max_buffer_size, 64 * 1024 * 1024);
assert!(!config.low_memory_mode);
}
#[test]
fn test_wasm_memory_config_low_memory() {
let mut config = WasmMemoryConfig::new();
config.chunk_size = 128 * 1024;
config.max_buffer_size = 128 * 1024 * 1024;
config.set_low_memory_mode(true);
assert!(config.low_memory_mode());
assert!(config.chunk_size <= 16 * 1024);
assert!(config.max_buffer_size <= 8 * 1024 * 1024);
}
#[test]
fn test_wasm_memory_config_minimum_values() {
let mut config = WasmMemoryConfig::new();
config.set_chunk_size(100); assert!(config.chunk_size >= 1024);
config.set_max_buffer_size(100); assert!(config.max_buffer_size >= 1024 * 1024);
}
}