use std::ptr::NonNull;
use std::time::Duration;
use samp_sdk::amx::Amx;
use samp_sdk::cell::AmxCell;
use crate::runtime::Runtime;
#[doc(hidden)]
pub fn initialize<F, T>(constructor: F)
where
F: FnOnce() -> T + 'static,
T: SampPlugin + 'static,
{
let rt = Runtime::initialize();
let plugin = constructor();
rt.set_plugin(plugin);
rt.post_initialize();
}
#[derive(Debug, Clone, Copy)]
pub struct TickConfig {
pub sa_mp: bool,
pub omp: bool,
pub omp_interval: Duration,
}
impl Default for TickConfig {
fn default() -> Self {
Self {
sa_mp: true,
omp: true,
omp_interval: Duration::from_millis(5),
}
}
}
impl TickConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn sa_mp(mut self, enabled: bool) -> Self {
self.sa_mp = enabled;
self
}
#[must_use]
pub fn omp(mut self, enabled: bool) -> Self {
self.omp = enabled;
self
}
#[must_use]
pub fn omp_interval(mut self, interval: Duration) -> Self {
self.omp_interval = interval;
self
}
#[must_use]
pub fn sa_mp_only() -> Self {
Self::default().omp(false)
}
#[must_use]
pub fn omp_only(interval: Duration) -> Self {
Self::default().sa_mp(false).omp_interval(interval)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TickSource {
SaMp,
OmpTimer,
}
#[derive(Debug, Clone, Copy)]
pub struct TickContext {
pub elapsed: Duration,
pub source: TickSource,
}
pub fn enable_tick() {
enable_tick_with(TickConfig::default());
}
pub fn enable_tick_with(config: TickConfig) {
Runtime::get().set_tick_config(config);
}
pub fn enable_debug_hook(amx: &Amx) {
amx.install_debug_hook(debug_hook_trampoline);
}
pub fn disable_debug_hook(amx: &Amx) {
amx.remove_debug_hook();
}
extern "C" fn debug_hook_trampoline(amx: *mut samp_sdk::raw::types::AMX) -> i32 {
let _ = std::panic::catch_unwind(|| {
let Some(rt) = Runtime::try_get() else { return };
let wrapped = Amx::new(amx, rt.amx_exports());
Runtime::plugin().on_debug_break(&wrapped);
});
0 }
pub fn logger() -> fern::Dispatch {
let rt = Runtime::get();
rt.disable_default_logger();
fern::Dispatch::new().chain(fern::Output::call(|record| {
let rt = Runtime::get();
#[cfg(not(feature = "samp-only"))]
{
let level = match record.level() {
log::Level::Error => samp_sdk::omp::LogLevel::Error,
log::Level::Warn => samp_sdk::omp::LogLevel::Warning,
log::Level::Info => samp_sdk::omp::LogLevel::Message,
log::Level::Debug | log::Level::Trace => samp_sdk::omp::LogLevel::Debug,
};
rt.log_level(level, record.args());
}
#[cfg(feature = "samp-only")]
rt.log(record.args());
}))
}
#[doc(hidden)]
#[must_use]
pub fn get<T: SampPlugin + 'static>() -> NonNull<T> {
Runtime::plugin_cast()
}
#[cfg(not(feature = "samp-only"))]
#[must_use]
pub fn omp_core() -> Option<*mut samp_sdk::omp::component::ICore> {
crate::runtime::Runtime::get().omp_core()
}
#[cfg(not(feature = "samp-only"))]
#[must_use]
pub fn omp_query_component(
uid: samp_sdk::omp::types::UID,
) -> Option<*mut samp_sdk::omp::server::ServerComponent> {
crate::runtime::Runtime::get().omp_query_component(uid)
}
#[cfg(not(feature = "samp-only"))]
#[must_use]
pub fn omp_query<T>() -> Option<T>
where
T: samp_sdk::omp::OmpComponentHandle,
{
let raw = omp_query_component(T::UID)?;
let nonnull_ptr = std::ptr::NonNull::new(raw)?;
Some(unsafe { T::from_raw(nonnull_ptr) })
}
pub trait SampPlugin {
fn on_load(&mut self) {}
fn on_unload(&mut self) {}
fn on_amx_load(&mut self, amx: &Amx) {
let _ = amx;
}
fn on_amx_unload(&mut self, amx: &Amx) {
let _ = amx;
}
fn on_debug_break(&mut self, amx: &Amx) {
let _ = amx;
}
fn on_tick(&mut self, ctx: TickContext) {
let _ = ctx;
}
#[cfg(not(feature = "samp-only"))]
fn on_omp_ready(&mut self) {}
#[cfg(not(feature = "samp-only"))]
fn on_component_free(&mut self) {}
}
#[doc(hidden)]
pub fn convert_return_value<T: AmxCell<'static>>(value: T) -> i32 {
value.as_cell()
}
#[must_use]
pub fn pawn_include() -> String {
let rt = Runtime::get();
render_pawn_include(rt.plugin_name(), rt.native_decls())
}
fn render_pawn_include(name: &str, decls: &[&str]) -> String {
let guard: String = name
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
.collect();
let mut out = format!(
"// Generated by rust-samp from the #[native] signatures of {name}.\n\
// Edits are lost on the next build — change the Rust side instead.\n\n\
#if defined _{guard}_included\n #endinput\n#endif\n#define _{guard}_included\n\n"
);
for decl in decls {
out.push_str(decl);
out.push('\n');
}
out
}
pub fn write_pawn_include(path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
std::fs::write(path, pawn_include())
}
pub(crate) fn write_pawn_include_if_requested() {
let Some(path) = std::env::var_os("SAMP_PAWN_INCLUDE") else {
return;
};
match write_pawn_include(&path) {
Ok(()) => crate::macros::sdk_info!("Pawn include written to {}", path.to_string_lossy()),
Err(e) => crate::macros::sdk_warn!(
"could not write the Pawn include to {}: {e}",
path.to_string_lossy()
),
}
}
#[cfg(test)]
mod pawn_include_tests {
use super::render_pawn_include;
#[test]
fn header_names_the_plugin_and_declarations_follow() {
let out = render_pawn_include("counter", &["native Counter_Get(&out);"]);
assert!(out.contains("signatures of counter"));
assert!(out.ends_with("native Counter_Get(&out);\n"));
}
#[test]
fn include_guard_drops_characters_pawn_rejects() {
let out = render_pawn_include("email-samp", &[]);
assert!(out.contains("#define _email_samp_included"));
assert!(!out.contains("email-samp_included"));
}
#[test]
fn a_plugin_without_natives_still_produces_a_valid_include() {
let out = render_pawn_include("hello", &[]);
assert!(out.contains("#if defined _hello_included"));
}
}