Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 65 additions & 33 deletions rustuna_core/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,6 @@ pub trait Storage: Send + Sync {
fn may_omit_trials(&self) -> bool;
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
/// Options for [`InMemoryStorage`].
pub struct InMemoryStorageOptions {
pub apply_discard: bool,
}

/// In-memory storage implementation used by default in Rust code and tests.
///
/// This implementation keeps all studies, trials, and caches in process memory.
Expand All @@ -180,25 +174,45 @@ pub struct InMemoryStorage {
// Supports the state-counting API required when `discard_trials` removes trials.
// Storing only discarded trials' states avoids tracking state transitions and keeps this simple.
discarded_state_counts: HashMap<(u32, TrialState), u32>,
option: InMemoryStorageOptions,
apply_discard: bool,
}
impl InMemoryStorage {
/// Creates an empty in-memory storage.
pub fn new() -> InMemoryStorage {
InMemoryStorage {
studies: vec![],
trials: HashMap::new(),
trial_id_number_map: TrialIdNumberHashMap::new(),
study_caches: HashMap::new(),
next_study_id: 0,
next_trial_id: 0,
discarded_state_counts: HashMap::new(),
option: InMemoryStorageOptions::default(),
/// Builder for [`InMemoryStorage`], following the API style of [`std::thread::Builder`].
///
/// # Examples
///
/// ```
/// use rustuna_core::storage::InMemoryStorage;
///
/// let storage = InMemoryStorage::builder().apply_discard(true).build();
/// # let _ = storage;
/// ```
#[derive(Debug)]
pub struct InMemoryStorageBuilder {
apply_discard: bool,
}
impl Default for InMemoryStorageBuilder {
fn default() -> Self {
Self::new()
}
}
impl InMemoryStorageBuilder {
/// Creates a builder with the default configuration.
pub fn new() -> Self {
Self {
apply_discard: false,
}
}

/// Creates an empty in-memory storage.
pub fn new_with_option(option: InMemoryStorageOptions) -> InMemoryStorage {
/// Sets whether [`Storage::discard_trials`] removes trials from this storage.
///
/// When this is `false`, discarding is a no-op. When this is `true`, discarded trials
/// are omitted from subsequent reads.
pub fn apply_discard(self, apply_discard: bool) -> Self {
Self { apply_discard }
}

/// Builds the storage.
pub fn build(self) -> InMemoryStorage {
InMemoryStorage {
studies: vec![],
trials: HashMap::new(),
Expand All @@ -207,9 +221,33 @@ impl InMemoryStorage {
next_study_id: 0,
next_trial_id: 0,
discarded_state_counts: HashMap::new(),
option,
apply_discard: self.apply_discard,
}
}
}

impl InMemoryStorage {
/// Returns a builder for creating a storage with an explicit configuration.
///
/// This is the counterpart of [`std::thread::Builder`]: settings are configured by
/// chaining methods and the storage is created with [`InMemoryStorageBuilder::build`].
///
/// # Examples
///
/// ```
/// use rustuna_core::storage::InMemoryStorage;
///
/// let storage = InMemoryStorage::builder().apply_discard(true).build();
/// # let _ = storage;
/// ```
pub fn builder() -> InMemoryStorageBuilder {
InMemoryStorageBuilder::new()
}

/// Creates an empty in-memory storage.
pub fn new() -> InMemoryStorage {
Self::builder().build()
}

pub fn insert_study_with_id(
&mut self,
Expand Down Expand Up @@ -622,7 +660,7 @@ impl Storage for InMemoryStorage {
}

fn discard_trials(&mut self, trial_ids: &[u32]) -> Result<()> {
if !self.option.apply_discard {
if !self.apply_discard {
return Ok(());
}
for trial_id in trial_ids {
Expand All @@ -647,7 +685,7 @@ impl Storage for InMemoryStorage {
}

fn may_omit_trials(&self) -> bool {
self.option.apply_discard
self.apply_discard
}
}

Expand Down Expand Up @@ -803,9 +841,7 @@ mod tests {

#[test]
fn delete_study_removes_discarded_trial_mappings() -> Result<()> {
let mut storage = InMemoryStorage::new_with_option(InMemoryStorageOptions {
apply_discard: true,
});
let mut storage = InMemoryStorage::builder().apply_discard(true).build();
let study_id = storage
.create_new_study("study", vec![Direction::Minimize])?
.id;
Expand Down Expand Up @@ -910,9 +946,7 @@ mod tests {

#[test]
fn discard_trials_omits_trials() -> Result<()> {
let mut storage = InMemoryStorage::new_with_option(InMemoryStorageOptions {
apply_discard: true,
});
let mut storage = InMemoryStorage::builder().apply_discard(true).build();
let study_id = storage
.create_new_study("study", vec![Direction::Minimize])?
.id;
Expand All @@ -933,9 +967,7 @@ mod tests {

#[test]
fn get_n_trials_counts_states() -> Result<()> {
let mut storage = InMemoryStorage::new_with_option(InMemoryStorageOptions {
apply_discard: true,
});
let mut storage = InMemoryStorage::builder().apply_discard(true).build();
let study_id = storage
.create_new_study("study", vec![Direction::Minimize])?
.id;
Expand Down
16 changes: 9 additions & 7 deletions rustuna_pyo3/src/storage/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::sync::{Arc, RwLock};

use pyo3::prelude::*;

use rustuna_core::storage::{InMemoryStorage, InMemoryStorageOptions, Storage};
use rustuna_core::storage::{InMemoryStorage, Storage};

use crate::distribution::PyDistribution;
use crate::storage::binding::StorageBinding;
Expand All @@ -18,15 +18,17 @@ pub struct PyInMemoryStorage {

impl Default for PyInMemoryStorage {
fn default() -> Self {
Self::new(InMemoryStorageOptions::default())
Self::new(false)
}
}

impl PyInMemoryStorage {
pub fn new(option: InMemoryStorageOptions) -> Self {
let binding = StorageBinding::new(Arc::new(RwLock::new(InMemoryStorage::new_with_option(
option,
))));
pub fn new(apply_discard: bool) -> Self {
let binding = StorageBinding::new(Arc::new(RwLock::new(
InMemoryStorage::builder()
.apply_discard(apply_discard)
.build(),
)));
PyInMemoryStorage { binding }
}

Expand All @@ -40,7 +42,7 @@ impl PyInMemoryStorage {
#[new]
#[pyo3(signature = (*, apply_discard = false))]
fn py_new(apply_discard: bool) -> Self {
PyInMemoryStorage::new(InMemoryStorageOptions { apply_discard })
PyInMemoryStorage::new(apply_discard)
}

fn create_new_study(
Expand Down
13 changes: 7 additions & 6 deletions rustuna_pyo3/src/storage/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pyo3::prelude::*;

use rustuna_core::storage::Storage;
use rustuna_storage::journal::file::JournalFileBackend;
use rustuna_storage::journal::storage::{JournalStorage, JournalStorageOptions};
use rustuna_storage::journal::storage::JournalStorage;

use crate::distribution::PyDistribution;
use crate::storage::binding::StorageBinding;
Expand Down Expand Up @@ -33,11 +33,12 @@ impl PyJournalFileStorage {
let backend = JournalFileBackend::new(file_path, None).map_err(|e| {
PyRuntimeError::new_err(format!("Failed to create journal file: {e:?}"))
})?;
let storage = JournalStorage::new_with_options(
Box::new(backend),
JournalStorageOptions { apply_discard },
)
.map_err(|e| PyRuntimeError::new_err(format!("Failed to create journal storage: {e:?}")))?;
let storage = JournalStorage::builder(Box::new(backend))
.apply_discard(apply_discard)
.build()
.map_err(|e| {
PyRuntimeError::new_err(format!("Failed to create journal storage: {e:?}"))
})?;
let binding = StorageBinding::new(Arc::new(RwLock::new(storage)));
Ok(PyJournalFileStorage { binding })
}
Expand Down
13 changes: 7 additions & 6 deletions rustuna_pyo3/src/storage/sqlite3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pyo3::prelude::*;

use rustuna_core::storage::Storage;
use rustuna_storage::cache::CachedStorage;
use rustuna_storage::sqlite3::{SQLite3Storage, SQLite3StorageOptions};
use rustuna_storage::sqlite3::SQLite3Storage;

use crate::distribution::PyDistribution;
use crate::storage::binding::StorageBinding;
Expand All @@ -30,11 +30,12 @@ impl PySQLite3Storage {
#[new]
#[pyo3(signature = (file_path, *, create_database = true, apply_discard = false))]
fn py_new(file_path: &str, create_database: bool, apply_discard: bool) -> PyResult<Self> {
let backend =
SQLite3Storage::new_with_option(file_path, SQLite3StorageOptions { apply_discard })
.map_err(|e| {
PyRuntimeError::new_err(format!("Failed to open the SQLite3 file: {e:?}"))
})?;
let backend = SQLite3Storage::builder(file_path)
.apply_discard(apply_discard)
.build()
.map_err(|e| {
PyRuntimeError::new_err(format!("Failed to open the SQLite3 file: {e:?}"))
})?;
if create_database {
backend.create_database().map_err(|e| {
PyRuntimeError::new_err(format!("Failed to create the database: {e:?}"))
Expand Down
8 changes: 5 additions & 3 deletions rustuna_storage/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ pub trait CachedStorageBackend: Send + Sync {
) -> Result<()>;
/// Whether reads from this backend omit discarded trials.
///
/// This mirrors `InMemoryStorageOptions::apply_discard` and
/// `JournalStorageOptions::apply_discard`: [`Self::discard_trials`] persists the discard
/// regardless of this flag, which only decides whether reads apply it.
/// This mirrors the `apply_discard` setting of the storage builders (e.g.
/// [`InMemoryStorageBuilder`](rustuna_core::storage::InMemoryStorageBuilder) and
/// [`JournalStorageBuilder`](crate::journal::storage::JournalStorageBuilder)):
/// [`Self::discard_trials`] persists the discard regardless of this flag, which only
/// decides whether reads apply it.
fn apply_discard(&self) -> bool {
false
}
Expand Down
Loading
Loading