use core::ffi::c_void;
use crate::abi::{Event, Status, Str};
use crate::config::{Config, ConfigError, Stage, reason};
use crate::host::Host;
use crate::locks::Relock;
use super::{CHECK_WHY, Kept, Load, Off, RESTART, STARTED, keep, with_server};
impl Load<'_> {
pub fn config<T: Config>(&mut self) -> Result<T, Off> {
self.host.config_schema(&T::schema().to_text());
self.config_check::<T>();
read_config(self.host, |settings: &mut T| {
keep(&STARTED, settings, Stage::Startup);
keep(&RESTART, settings, Stage::Restart);
})
}
pub fn on_config_change<T, F>(&mut self, handler: F) -> Result<(), Off>
where
T: Config,
F: Fn(T) + Send + Sync + 'static,
{
let handler = std::sync::Arc::new(handler);
let on_world = std::sync::Arc::clone(&handler);
self.on_raw_event(Event::SETTINGS_REPLACED, move |_| {
let Some(host) = crate::host() else {
return;
};
let world = |settings: &mut T| {
hold(&RESTART, settings, Stage::Restart);
keep(&STARTED, settings, Stage::Startup);
};
match read_config::<T>(host, world) {
Ok(settings) => {
on_world(settings);
crate::info!("the world's settings apply");
}
Err(off) => crate::warn!("the world's settings are skipped: {off}"),
}
})?;
self.on_raw_event(Event::SETTINGS_CHANGED, move |_| {
let Some(host) = crate::host() else {
return;
};
let running = |settings: &mut T| {
hold(&RESTART, settings, Stage::Restart);
hold(&STARTED, settings, Stage::Startup);
};
match read_config::<T>(host, running) {
Ok(settings) => {
handler(settings);
crate::info!("settings reloaded");
}
Err(off) => crate::warn!("settings change skipped, the running values stay: {off}"),
}
})
}
}
pub(super) fn hold<T: Config>(kept: &Kept, settings: &mut T, stage: Stage) {
let kept = kept.get_or_init(std::sync::Mutex::default).relock().clone();
let (Some(kept), Some(now)) = (kept, settings.values(stage)) else {
return;
};
if kept == now {
return;
}
let waits = if stage == Stage::Restart {
"the game starts again"
} else {
"the world loads"
};
match settings.merge_values(stage, &kept) {
Ok(()) => crate::info!(
"settings that take effect when {waits} changed: {}",
crate::config::changed_keys(&kept, &now)
),
Err(why) => crate::warn!("{} settings not kept: {why}", stage.name()),
}
}
pub(super) unsafe extern "system" fn check_text<T: Config>(
text: Str,
why: *mut Str,
_user: *mut c_void,
) -> Status {
crate::host::guard(
"panic in the settings check",
|| Status::REFUSED,
|| {
let text = unsafe { text.as_str() }.unwrap_or("");
let checked = toml::from_str::<T>(text)
.map_err(|e| ConfigError::Parse(reason(Some(text), &e)))
.and_then(|settings| settings.validate());
let Err(reason) = checked else {
return Status::OK;
};
CHECK_WHY.with_borrow_mut(|kept| {
*kept = reason.to_reason();
if !why.is_null() {
unsafe { why.write(Str::new(kept)) };
}
});
Status::REFUSED
},
)
}
pub(super) fn read_config<T: Config>(host: Host, stages: impl FnOnce(&mut T)) -> Result<T, Off> {
let text = host.settings_text(&T::template())?;
let mut table: toml::Table = toml::from_str(&text)
.map_err(|e| Off::new(format!("settings: {}", reason(Some(&text), &e))))?;
for held in T::schema().clamp(&mut table) {
crate::warn!("settings: {held}");
}
let mut settings: T = toml::Value::Table(table)
.try_into()
.map_err(|e: toml::de::Error| Off::new(format!("settings: {}", reason(None, &e))))?;
settings
.validate()
.map_err(|why| Off::new(format!("settings: {why}")))?;
stages(&mut settings);
let Some(server) = settings.synced().and_then(|local| host.sync(&local)) else {
return Ok(settings);
};
match with_server(&mut settings, &server) {
Ok(()) => crate::info!("the server's join values apply over the settings file: {server}"),
Err(why) => crate::warn!("the server's join values are refused, the file's apply: {why}"),
}
Ok(settings)
}