use core::ffi::c_void;
use std::borrow::Cow;
use settings::check_text;
use crate::abi::{HostApi, Status, Str};
use crate::config::{Config, ConfigError, Stage};
use crate::host::Host;
use crate::locks::Relock;
use crate::services::{ModEvent, Service};
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("{reason}")]
pub struct Off {
reason: Cow<'static, str>,
}
impl Off {
#[must_use]
pub fn new(reason: impl Into<Cow<'static, str>>) -> Self {
Self {
reason: reason.into(),
}
}
pub(crate) fn status(what: &str, status: Status) -> Self {
let why = match status {
Status::MISSING => "missing on this game build",
Status::AMBIGUOUS => "ambiguous on this game build",
Status::REFUSED => "refused by the loader",
Status::BAD_ARGS => "malformed",
Status::TOO_LATE => "asked for too late",
Status::FAULTED => "unavailable: the mod has faulted",
Status::WRONG_THREAD => "called off the thread it serves",
_ => "failed",
};
Self::new(format!("{what}: {why}"))
}
}
macro_rules! off_from {
($($error:ty),* $(,)?) => {$(
impl From<$error> for Off {
fn from(error: $error) -> Self {
Self::new(error.to_string())
}
}
)*};
}
off_from!(
crate::affixes::RollError,
crate::altars::ResizeError,
crate::code::FindError,
crate::code::PatternError,
crate::console::RunError,
ConfigError,
crate::content::vanilla::VanillaError,
crate::inventory::BatchError,
crate::item_data::SetError,
crate::migrations::MigrationError,
crate::net::SendError,
crate::packs::PackError,
crate::packs::ReadPackError,
crate::rates::RateError,
crate::services::LayoutError,
crate::stats::ModifyError,
crate::ui::UiError,
crate::world::WorldError,
);
pub trait Mod: Sized + Send + Sync + 'static {
fn manifest() -> &'static crate::abi::Manifest;
fn init(load: &mut Load<'_>) -> Result<Self, Off>;
fn ready(&'static self, _host: Host) {}
fn shutdown(&'static self) {}
}
#[derive(Debug)]
pub struct Load<'a> {
host: Host,
joins: bool,
_init: core::marker::PhantomData<&'a ()>,
}
impl Load<'_> {
pub(crate) fn new(host: Host) -> Self {
Self::of(host, false)
}
pub(crate) fn of(host: Host, joins: bool) -> Self {
Self {
host,
joins,
_init: core::marker::PhantomData,
}
}
pub(crate) fn joins(&self) -> bool {
self.joins
}
#[must_use]
pub fn host(&self) -> Host {
self.host
}
fn api(&self) -> &'static HostApi {
self.host.api()
}
pub fn world(&mut self) -> Result<crate::world::World, Off> {
crate::world::World::resolve(self)
}
pub fn inventories(&mut self) -> Result<crate::inventory::Inventories, Off> {
crate::inventory::Inventories::resolve(self)
}
pub fn scheduler(&mut self) -> Result<crate::schedule::Scheduler, Off> {
let (scheduler, subscribe) = crate::schedule::Scheduler::get();
if subscribe
&& let Err(off) = self.on::<crate::events::Ticked>(move |_| scheduler.run_due())
{
scheduler.unsubscribed();
return Err(off);
}
Ok(scheduler)
}
pub fn data_dir(&self) -> Result<std::path::PathBuf, Off> {
let mut out = Str::EMPTY;
let status = (self.api().data_dir)(self.host.handle(), &raw mut out);
ok(status, (), || "data folder".to_owned())?;
let path = unsafe { out.as_str() }.ok_or_else(|| Off::new("data folder: not UTF-8"))?;
Ok(std::path::PathBuf::from(path))
}
pub fn world_store<T>(&mut self, name: &str) -> Result<crate::state::WorldStore<T>, Off>
where
T: serde::de::DeserializeOwned + serde::Serialize + Default + Send + 'static,
{
let store = crate::state::WorldStore::new(&self.data_dir()?, name)?;
let host = self.host;
let opened = store.clone();
self.on::<crate::events::WorldLoaded>(move |()| match crate::world::world_key(host) {
Some(key) => {
if let Err(error) = opened.open(&key) {
crate::error!("world store kept shut: {error}");
}
}
None => crate::warn!("world store kept shut: the loader names no world"),
})?;
let saved = store.clone();
self.on::<crate::events::WorldSaved>(move |()| saved.flush_in_background())?;
let closed = store.clone();
self.on::<crate::events::ShutdownStarted>(move |()| {
if let Err(error) = closed.close() {
crate::error!("{error}");
}
})?;
Ok(store)
}
}
fn service_spec<T: Service>(table: *const c_void) -> crate::abi::ServiceSpec {
crate::abi::ServiceSpec {
header: crate::abi::Header::of::<crate::abi::ServiceSpec>(),
name: crate::services::name(T::NAME),
major: T::MAJOR,
layout: crate::services::layout(T::LAYOUT, size_of::<T>()),
table,
}
}
fn event_spec<E: ModEvent>() -> crate::abi::EventSpec {
crate::abi::EventSpec {
header: crate::abi::Header::of::<crate::abi::EventSpec>(),
name: crate::services::name(E::NAME),
layout: crate::services::layout(E::LAYOUT, size_of::<E>()),
}
}
static STARTED: Kept = Kept::new();
static RESTART: Kept = Kept::new();
type Kept = crate::fresh::Fresh<std::sync::Mutex<Option<String>>>;
fn keep<T: Config>(kept: &Kept, settings: &T, stage: Stage) {
*kept.get_or_init(std::sync::Mutex::default).relock() =
Some(settings.values(stage).unwrap_or_default());
}
std::thread_local! {
static CHECK_WHY: core::cell::RefCell<String> = const { core::cell::RefCell::new(String::new()) };
}
impl Load<'_> {
fn config_check<T: Config>(&self) {
let api = self.api();
let _checked =
(api.config_check)(self.host.handle(), check_text::<T>, core::ptr::null_mut());
}
}
fn with_server<T: Config>(settings: &mut T, server: &str) -> Result<(), ConfigError> {
let own = settings.synced().unwrap_or_default();
settings.merge_synced(server)?;
settings.validate().inspect_err(|_| {
let _back = settings.merge_synced(&own);
})
}
struct MidHook<F> {
address: u64,
handler: F,
}
pub(crate) fn ok<T>(status: Status, value: T, what: impl FnOnce() -> String) -> Result<T, Off> {
if status.is_ok() {
Ok(value)
} else {
Err(Off::status(&what(), status))
}
}
pub(crate) fn hand_over<T>(
value: T,
register: impl FnOnce(*mut c_void) -> Status,
what: &str,
) -> Result<(), Off> {
let user = Box::into_raw(Box::new(value));
let status = register(user.cast());
if !status.is_ok() {
drop(unsafe { Box::from_raw(user) });
}
ok(status, (), || what.to_owned())
}
mod build;
mod code;
mod events;
mod services;
mod settings;
#[cfg(test)]
mod tests;