use crate::{BlockNumber, WasmProgram, state::WithOverlay};
use gear_common::{ActorId, GearPage, MessageId, PageBuf, storage::DoubleBTreeMap};
use gear_core::{
pages::{WasmPage, numerated::tree::IntervalsTree},
program::Program,
};
use std::{collections::BTreeMap, fmt, thread::LocalKey};
pub(crate) const PLACEHOLDER_MESSAGE_ID: MessageId =
MessageId::new(*b"PLACEHOLDER_MESSAGE_ID\0\0\0\0\0\0\0\0\0\0");
#[derive(Debug)]
pub(crate) enum GTestProgram {
Default { primary: Program<BlockNumber> },
Mock {
primary: Program<BlockNumber>,
handlers: Box<dyn WasmProgram>,
},
}
impl GTestProgram {
pub(crate) fn as_primary_program(&self) -> &Program<BlockNumber> {
match self {
GTestProgram::Default { primary } => primary,
GTestProgram::Mock { primary, .. } => primary,
}
}
pub(crate) fn as_primary_program_mut(&mut self) -> &mut Program<BlockNumber> {
match self {
GTestProgram::Default { primary } => primary,
GTestProgram::Mock { primary, .. } => primary,
}
}
}
impl Clone for GTestProgram {
fn clone(&self) -> Self {
match self {
GTestProgram::Default { primary } => GTestProgram::Default {
primary: primary.clone(),
},
GTestProgram::Mock { primary, handlers } => GTestProgram::Mock {
primary: primary.clone(),
handlers: handlers.clone_boxed(),
},
}
}
}
type ProgramsStorage = WithOverlay<BTreeMap<ActorId, GTestProgram>>;
type AllocationsStorage = WithOverlay<BTreeMap<ActorId, IntervalsTree<WasmPage>>>;
type MemoryPagesStorage = WithOverlay<DoubleBTreeMap<ActorId, GearPage, PageBuf>>;
thread_local! {
static PROGRAMS_STORAGE: ProgramsStorage = WithOverlay::new(Default::default());
static ALLOCATIONS_STORAGE: AllocationsStorage = WithOverlay::new(Default::default());
static MEMORY_PAGES_STORAGE: MemoryPagesStorage = WithOverlay::new(Default::default());
}
fn programs_storage() -> &'static LocalKey<WithOverlay<BTreeMap<ActorId, GTestProgram>>> {
&PROGRAMS_STORAGE
}
fn allocations_storage()
-> &'static LocalKey<WithOverlay<BTreeMap<ActorId, IntervalsTree<WasmPage>>>> {
&ALLOCATIONS_STORAGE
}
fn memory_pages_storage()
-> &'static LocalKey<WithOverlay<DoubleBTreeMap<ActorId, GearPage, PageBuf>>> {
&MEMORY_PAGES_STORAGE
}
pub(crate) struct ProgramsStorageManager;
impl ProgramsStorageManager {
pub(crate) fn access_primary_program<R>(
program_id: ActorId,
access: impl FnOnce(Option<&Program<BlockNumber>>) -> R,
) -> R {
programs_storage().with(|storage| {
access(
storage
.data()
.get(&program_id)
.map(|gtest_program| gtest_program.as_primary_program()),
)
})
}
pub(crate) fn insert_program(program_id: ActorId, program: GTestProgram) -> bool {
programs_storage().with(|storage| storage.data_mut().insert(program_id, program).is_some())
}
pub(crate) fn modify_program<R>(
program_id: ActorId,
modify: impl FnOnce(Option<&mut GTestProgram>) -> R,
) -> R {
programs_storage().with(|storage| modify(storage.data_mut().get_mut(&program_id)))
}
pub(crate) fn has_program(program_id: ActorId) -> bool {
programs_storage().with(|storage| storage.data().contains_key(&program_id))
}
pub(crate) fn is_user(id: ActorId) -> bool {
programs_storage().with(|storage| storage.data().get(&id).is_none())
}
pub(crate) fn is_active_program(id: ActorId) -> bool {
programs_storage().with(|storage| {
storage
.data()
.get(&id)
.map(|gtest_program| gtest_program.as_primary_program().is_active())
.unwrap_or(false)
})
}
pub(crate) fn is_program(id: ActorId) -> bool {
!Self::is_user(id)
}
pub(crate) fn is_mock_program(id: ActorId) -> bool {
programs_storage().with(|storage| {
storage
.data()
.get(&id)
.map(|gtest_program| matches!(gtest_program, GTestProgram::Mock { .. }))
.unwrap_or(false)
})
}
pub(crate) fn program_ids() -> Vec<ActorId> {
programs_storage().with(|storage| storage.data().keys().copied().collect())
}
pub(crate) fn clear() {
programs_storage().with(|storage| storage.data_mut().clear());
allocations_storage().with(|storage| storage.data_mut().clear());
memory_pages_storage().with(|storage| storage.data_mut().clear());
}
pub(crate) fn allocations(program_id: ActorId) -> Option<IntervalsTree<WasmPage>> {
allocations_storage().with(|storage| storage.data().get(&program_id).cloned())
}
pub(crate) fn set_allocations(program_id: ActorId, allocations: IntervalsTree<WasmPage>) {
programs_storage().with(|storage| {
if let Some(program) = storage.data_mut().get_mut(&program_id)
&& let Program::Active(active_program) = program.as_primary_program_mut()
{
active_program.allocations_tree_len = u32::try_from(allocations.intervals_amount())
.unwrap_or_else(|err| {
unreachable!("allocations tree length is too big to fit into u32: {err}")
});
}
});
allocations_storage().with(|storage| {
storage.data_mut().insert(program_id, allocations);
});
}
pub(crate) fn program_page(program_id: ActorId, page: GearPage) -> Option<PageBuf> {
memory_pages_storage().with(|storage| storage.data().get(&program_id, &page).cloned())
}
pub(crate) fn program_pages(program_id: ActorId) -> BTreeMap<GearPage, PageBuf> {
memory_pages_storage().with(|storage| storage.data().iter_key(&program_id).collect())
}
pub(crate) fn set_program_page(program_id: ActorId, page: GearPage, buf: PageBuf) {
memory_pages_storage().with(|storage| {
storage.data_mut().insert(program_id, page, buf);
});
}
pub(crate) fn remove_program_page(program_id: ActorId, page: GearPage) {
memory_pages_storage().with(|storage| {
storage.data_mut().remove(program_id, page);
});
}
}
impl fmt::Debug for ProgramsStorageManager {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
programs_storage().with(|storage| {
f.debug_map()
.entries(
storage
.data()
.iter()
.map(|(k, v)| (k, v.as_primary_program())),
)
.finish()
})
}
}