use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use beamr::module::ModuleRegistry;
use beamr::namespace::NamespaceId;
use beamr::scheduler::{Scheduler, SchedulerConfig, SchedulerServices};
use beamr::term::Term;
use frame_core::capability::{Capability, CapabilityRequest, GrantProvenance, PathRoot};
use frame_core::component::{
ChildArgument, ChildSpec, ComponentId, ComponentMeta, SupervisionPolicy,
};
use frame_core::registry::ComponentRegistry;
use frame_core::supervision::LifecycleConfig;
use frame_state::{ComponentSchema, ComponentStateStore, EntityId, StateError};
const COMPONENT_BEAM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/{{GLEAM_NAME}}.beam"));
const FFI_BEAM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/{{GLEAM_NAME}}_ffi.beam"));
const CHILD: &str = "entity_actor";
const LIVENESS_MESSAGE: i64 = 0;
const STOP_MESSAGE: i64 = 2;
const ENTITY_BYTES: &[u8] = br#"{"kind":"welcome","message":"hello from {{NAME}}"}"#;
const SCHEDULER_THREADS: usize = 2;
const OPERATION_TIMEOUT: Duration = Duration::from_secs(3);
const MAX_RESTARTS: usize = 3;
const RESTART_WINDOW: Duration = Duration::from_secs(30);
pub fn run_application(state_path: &Path) -> Result<EntityId, Box<dyn std::error::Error>> {
let state_path = absolute(state_path)?;
let state = ComponentStateStore::open(&state_path)?;
let _reconcile_report = state.reconcile()?;
let scheduler = Arc::new(frame_core::composition::compose_scheduler(
SchedulerConfig {
thread_count: Some(SCHEDULER_THREADS),
..SchedulerConfig::default()
},
SchedulerServices::minimal(),
Arc::new(ModuleRegistry::new()),
)?);
let registry = ComponentRegistry::new(
Arc::clone(&scheduler),
LifecycleConfig {
operation_timeout: OPERATION_TIMEOUT,
},
);
let ffi = scheduler.hot_load_module(FFI_BEAM)?;
let id = component_id();
let need = storage_need(&state_path);
registry.register(component_meta(id, need.clone()), COMPONENT_BEAM.to_vec())?;
let store = match state.component_handle(id) {
Ok(active) => active,
Err(StateError::NotActive { component }) if component == id => {
state.declare_schema(id, ComponentSchema::lww())?;
state.install_storage(id)?
}
Err(error) => return Err(error.into()),
};
registry.host_capabilities().grant(
id,
need,
GrantProvenance {
granted_by: "generated composition host".to_owned(),
granted_at: SystemTime::now(),
},
)?;
registry.start(id)?;
if registry.probe_child(id, CHILD)? != LIVENESS_MESSAGE {
return Err("component liveness mailbox round-trip returned the wrong value".into());
}
let entity_id = store.put(ENTITY_BYTES)?;
let entity_witness = entity_witness(entity_id);
registry.send_child(id, CHILD, entity_witness)?;
if registry.probe_child(id, CHILD)? != entity_witness {
return Err("component entity-id mailbox round-trip returned the wrong value".into());
}
store.commit()?;
if store.get(entity_id)?.as_deref() != Some(ENTITY_BYTES) {
return Err("component store did not return the round-tripped entity".into());
}
registry.stop(id)?;
unload_support_module(&scheduler, ffi.module_name)?;
if scheduler.process_count() != 0 {
return Err("ordered stop leaked a live process tree".into());
}
scheduler.shutdown();
Ok(entity_id)
}
fn component_id() -> ComponentId {
ComponentId::derive("frame.generated", "{{NAME}}")
}
fn entity_witness(entity: EntityId) -> i64 {
let bytes = entity.as_bytes();
let raw = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
let witness = (raw & Term::SMALL_INT_MAX.cast_unsigned()).cast_signed();
if witness <= STOP_MESSAGE {
witness + STOP_MESSAGE + 1
} else {
witness
}
}
fn storage_need(path: &Path) -> CapabilityRequest {
CapabilityRequest::new(Capability::FsWrite {
root: PathRoot(path.to_path_buf()),
})
}
fn component_meta(id: ComponentId, need: CapabilityRequest) -> ComponentMeta {
ComponentMeta {
id,
name: "{{NAME}} component".to_owned(),
version: "0.1.0".to_owned(),
requires: Vec::new(),
provides: Vec::new(),
needs: vec![need],
children: vec![ChildSpec {
name: CHILD.to_owned(),
module: "{{GLEAM_NAME}}".to_owned(),
function: "run".to_owned(),
arguments: vec![ChildArgument::SupervisorPid],
liveness_message: LIVENESS_MESSAGE,
stop_message: STOP_MESSAGE,
}],
supervision: Some(SupervisionPolicy {
max_restarts: MAX_RESTARTS,
window: RESTART_WINDOW,
}),
}
}
fn unload_support_module(
scheduler: &Scheduler,
module: beamr::atom::Atom,
) -> Result<(), Box<dyn std::error::Error>> {
if scheduler.check_old_code(module) {
scheduler.purge_module(module)?;
}
if !scheduler.delete_module(module)
|| scheduler
.lookup_module_in(NamespaceId::DEFAULT, module)
.is_some()
{
return Err("support module remained loaded after ordered stop".into());
}
Ok(())
}
fn absolute(path: &Path) -> Result<PathBuf, std::io::Error> {
if path.is_absolute() {
Ok(path.to_path_buf())
} else {
Ok(std::env::current_dir()?.join(path))
}
}