use samp_sdk::consts::{ServerData, Supports};
#[cfg(not(feature = "samp-only"))]
use samp_sdk::omp::component::ICore;
#[cfg(not(feature = "samp-only"))]
use samp_sdk::omp::events::PawnEventHandler;
#[cfg(not(feature = "samp-only"))]
use samp_sdk::omp::server::{ServerComponent, ServerComponentList};
#[cfg(not(feature = "samp-only"))]
use samp_sdk::omp::timers::{ITimer, TimerTimeOutHandler};
#[cfg(not(feature = "samp-only"))]
use samp_sdk::raw::types::AMX_NATIVE_INFO;
use samp_sdk::raw::{functions::Logprintf, types::AMX};
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::ffi::CString;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::time::{Duration, Instant};
use crate::amx::{Amx, AmxIdent};
use crate::events::{EventHandler, EventInfo};
use crate::plugin::{SampPlugin, TickConfig};
static RUNTIME: AtomicPtr<Runtime> = AtomicPtr::new(std::ptr::null_mut());
struct RuntimeInner {
plugin: Option<NonNull<dyn SampPlugin + 'static>>,
tick_config: Option<TickConfig>,
last_tick_at: Option<Instant>,
server_exports: *const usize,
#[cfg(not(feature = "samp-only"))]
omp_amx_exports: Option<usize>,
#[cfg(not(feature = "samp-only"))]
omp_core: Option<NonNull<ICore>>,
#[cfg(not(feature = "samp-only"))]
omp_component_list: Option<NonNull<ServerComponentList>>,
#[cfg(not(feature = "samp-only"))]
pawn_event_handler: Option<NonNull<PawnEventHandler>>,
#[cfg(not(feature = "samp-only"))]
omp_natives: Vec<AMX_NATIVE_INFO>,
#[cfg(not(feature = "samp-only"))]
omp_pending_amx: Vec<*mut AMX>,
#[cfg(not(feature = "samp-only"))]
omp_tick_timer: Option<NonNull<ITimer>>,
#[cfg(not(feature = "samp-only"))]
omp_tick_handler: Option<NonNull<TimerTimeOutHandler>>,
amx_list: Vec<(AmxIdent, Amx)>,
events: Vec<EventInfo>,
resolved_events: HashMap<(AmxIdent, i32), Vec<EventHandler>>,
logger_enabled: bool,
}
pub struct Runtime {
inner: UnsafeCell<RuntimeInner>,
}
unsafe impl Sync for Runtime {}
unsafe impl Send for Runtime {}
impl Runtime {
#[inline]
#[allow(clippy::mut_from_ref)]
fn inner(&self) -> &mut RuntimeInner {
unsafe { &mut *self.inner.get() }
}
pub fn initialize() -> &'static Runtime {
let inner = RuntimeInner {
plugin: None,
tick_config: None,
last_tick_at: None,
server_exports: std::ptr::null(),
#[cfg(not(feature = "samp-only"))]
omp_amx_exports: None,
#[cfg(not(feature = "samp-only"))]
omp_core: None,
#[cfg(not(feature = "samp-only"))]
omp_component_list: None,
#[cfg(not(feature = "samp-only"))]
pawn_event_handler: None,
#[cfg(not(feature = "samp-only"))]
omp_natives: Vec::new(),
#[cfg(not(feature = "samp-only"))]
omp_pending_amx: Vec::new(),
#[cfg(not(feature = "samp-only"))]
omp_tick_timer: None,
#[cfg(not(feature = "samp-only"))]
omp_tick_handler: None,
amx_list: Vec::new(),
events: Vec::new(),
resolved_events: HashMap::new(),
logger_enabled: true,
};
let rt = Runtime {
inner: UnsafeCell::new(inner),
};
let boxed = Box::new(rt);
RUNTIME.store(Box::into_raw(boxed), Ordering::Release);
Runtime::get()
}
pub fn post_initialize(&self) {
if !self.inner().logger_enabled {
return;
}
let logger = crate::plugin::logger();
let _ = logger.apply();
}
#[inline]
pub fn amx_exports(&self) -> usize {
let inner = self.inner();
#[cfg(not(feature = "samp-only"))]
if let Some(exports) = inner.omp_amx_exports {
return exports;
}
if inner.server_exports.is_null() {
return 0;
}
unsafe {
inner
.server_exports
.offset(ServerData::AmxExports.into())
.read()
}
}
#[inline]
pub fn logger(&self) -> Logprintf {
let inner = self.inner();
assert!(
!inner.server_exports.is_null(),
"server_exports not initialized"
);
unsafe {
inner
.server_exports
.offset(ServerData::Logprintf.into())
.cast::<Logprintf>()
.read()
}
}
pub fn disable_default_logger(&self) {
self.inner().logger_enabled = false;
}
pub fn log<T: std::fmt::Display>(&self, message: T) {
if !self.inner().server_exports.is_null() {
let log_fn = self.logger();
let msg = format!("{message}");
if let Ok(cstr) = CString::new(msg) {
log_fn(cstr.as_ptr());
}
return;
}
#[cfg(not(feature = "samp-only"))]
if let Some(core) = self.omp_core() {
let msg = format!("{message}");
if unsafe {
samp_sdk::omp::core_log_ln_u8(core, samp_sdk::omp::LogLevel::Message, &msg)
} {
return;
}
}
eprintln!("{message}");
}
#[cfg(not(feature = "samp-only"))]
pub fn log_level<T: std::fmt::Display>(&self, level: samp_sdk::omp::LogLevel, message: T) {
if self.inner().server_exports.is_null() {
if let Some(core) = self.omp_core() {
let msg = format!("{message}");
if unsafe { samp_sdk::omp::core_log_ln_u8(core, level, &msg) } {
return;
}
}
eprintln!("{message}");
return;
}
self.log(message);
}
pub fn insert_amx(&self, amx: *mut AMX) -> &Amx {
let inner = self.inner();
let ident = AmxIdent::from(amx);
let amx = Amx::new(amx, self.amx_exports());
inner.amx_list.push((ident, amx));
&inner
.amx_list
.last()
.expect("Vec::last() after push() always returns Some")
.1
}
pub fn remove_amx(&self, amx: *mut AMX) -> Option<Amx> {
let list = &mut self.inner().amx_list;
let ident = AmxIdent::from(amx);
list.iter()
.position(|(k, _)| *k == ident)
.map(|pos| list.swap_remove(pos).1)
}
pub fn supports(&self) -> Supports {
let mut supports = Supports::VERSION | Supports::AMX_NATIVES;
if self.tick_enabled_for_sa_mp() {
supports.insert(Supports::PROCESS_TICK);
}
supports
}
#[inline]
pub fn amx_list(&self) -> &[(AmxIdent, Amx)] {
&self.inner().amx_list
}
pub fn set_plugin<T>(&self, plugin: T)
where
T: SampPlugin + 'static,
{
let boxed = Box::new(plugin);
self.inner().plugin = NonNull::new(Box::into_raw(boxed));
}
pub fn set_server_exports(&self, exports: *const usize) {
self.inner().server_exports = exports;
}
pub fn set_tick_config(&self, config: TickConfig) {
self.inner().tick_config = Some(config);
}
#[inline]
pub fn tick_config(&self) -> Option<TickConfig> {
self.inner().tick_config
}
#[inline]
pub fn tick_enabled_for_sa_mp(&self) -> bool {
self.tick_config().is_some_and(|c| c.sa_mp)
}
#[cfg(not(feature = "samp-only"))]
#[inline]
pub fn omp_tick_interval(&self) -> Option<Duration> {
self.tick_config().filter(|c| c.omp).map(|c| c.omp_interval)
}
pub fn record_tick(&self) -> Duration {
let now = Instant::now();
let prev = self.inner().last_tick_at.replace(now);
prev.map_or(Duration::ZERO, |t| now.duration_since(t))
}
#[inline]
pub fn get() -> &'static Runtime {
let ptr = RUNTIME.load(Ordering::Acquire);
assert!(
!ptr.is_null(),
"Runtime::get() called before Runtime::initialize()"
);
unsafe { &*ptr }
}
#[inline]
pub fn try_get() -> Option<&'static Runtime> {
let ptr = RUNTIME.load(Ordering::Acquire);
if ptr.is_null() {
None
} else {
Some(unsafe { &*ptr })
}
}
#[inline]
pub fn plugin() -> &'static mut dyn SampPlugin {
let rt = Runtime::get();
let inner = rt.inner();
unsafe {
inner
.plugin
.as_mut()
.expect("Runtime::plugin() called before set_plugin()")
.as_mut()
}
}
#[inline]
pub fn plugin_cast<T: SampPlugin>() -> NonNull<T> {
let rt = Runtime::get();
rt.inner()
.plugin
.as_ref()
.expect("Runtime::plugin_cast() called before set_plugin()")
.cast()
}
pub fn register_events(&self, mut events: Vec<EventInfo>) {
self.inner().events.append(&mut events);
}
#[inline]
pub fn has_events(&self) -> bool {
!self.inner().events.is_empty()
}
pub fn events_snapshot(&self) -> Vec<EventInfo> {
self.inner().events.clone()
}
pub fn push_resolved_event(&self, ident: AmxIdent, index: i32, handler: EventHandler) {
self.inner()
.resolved_events
.entry((ident, index))
.or_default()
.push(handler);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn resolved_handlers(&self, ident: AmxIdent, index: i32) -> Vec<EventHandler> {
self.inner()
.resolved_events
.get(&(ident, index))
.cloned()
.unwrap_or_default()
}
pub fn remove_resolved_events(&self, ident: AmxIdent) {
self.inner().resolved_events.retain(|(k, _), _| *k != ident);
}
}
#[cfg(not(feature = "samp-only"))]
impl Runtime {
pub fn set_omp_amx_exports(&self, exports: usize) {
self.inner().omp_amx_exports = Some(exports);
}
pub fn omp_has_amx_exports(&self) -> bool {
self.inner().omp_amx_exports.is_some()
}
pub fn set_omp_core(&self, core: *mut ICore) {
self.inner().omp_core = NonNull::new(core);
}
pub fn omp_core(&self) -> Option<*mut ICore> {
self.inner().omp_core.map(std::ptr::NonNull::as_ptr)
}
pub fn set_omp_component_list(&self, list: *mut ServerComponentList) {
self.inner().omp_component_list = NonNull::new(list);
}
pub fn omp_component_list(&self) -> Option<*mut ServerComponentList> {
self.inner()
.omp_component_list
.map(std::ptr::NonNull::as_ptr)
}
pub fn omp_query_component(
&self,
uid: samp_sdk::omp::types::UID,
) -> Option<*mut ServerComponent> {
let list = self.inner().omp_component_list?.as_ptr();
let ptr = unsafe { samp_sdk::omp::server::query_component(list, uid) };
if ptr.is_null() { None } else { Some(ptr) }
}
pub fn set_pawn_event_handler(&self, handler: *mut PawnEventHandler) {
self.inner().pawn_event_handler = NonNull::new(handler);
}
pub fn take_pawn_event_handler(&self) -> Option<*mut PawnEventHandler> {
self.inner()
.pawn_event_handler
.take()
.map(std::ptr::NonNull::as_ptr)
}
pub fn set_omp_natives(&self, natives: Vec<AMX_NATIVE_INFO>) {
self.inner().omp_natives = natives;
}
pub fn omp_natives(&self) -> &[AMX_NATIVE_INFO] {
&self.inner().omp_natives
}
pub fn enqueue_pending_amx(&self, amx: *mut AMX) {
self.inner().omp_pending_amx.push(amx);
}
pub fn take_pending_amx(&self) -> Vec<*mut AMX> {
std::mem::take(&mut self.inner().omp_pending_amx)
}
pub fn set_omp_tick(&self, timer: *mut ITimer, handler: *mut TimerTimeOutHandler) {
self.inner().omp_tick_timer = NonNull::new(timer);
self.inner().omp_tick_handler = NonNull::new(handler);
}
pub fn take_omp_tick_timer(&self) -> Option<*mut ITimer> {
self.inner()
.omp_tick_timer
.take()
.map(std::ptr::NonNull::as_ptr)
}
pub fn take_omp_tick_handler(&self) -> Option<*mut TimerTimeOutHandler> {
self.inner()
.omp_tick_handler
.take()
.map(std::ptr::NonNull::as_ptr)
}
}