use std::error::Error;
use std::ffi::c_int;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::hint;
use std::num::NonZero;
pub const ABSOLUTE_PLAYER_LIMIT: c_int = 255;
const RAW_USER_ID_MAX: c_int = u16::MAX as c_int;
#[doc(alias = "userid")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct UserId(NonZero<u16>);
impl UserId {
pub const fn new(user_id: u16) -> Option<Self> {
match NonZero::new(user_id) {
Some(user_id) => Some(Self(user_id)),
None => None,
}
}
pub const fn from_raw(raw: c_int) -> Result<Self, InvalidUserId> {
match raw {
1..=RAW_USER_ID_MAX => Ok(Self(NonZero::new(raw as u16).unwrap())),
erroneous => Err(InvalidUserId(erroneous)),
}
}
pub const fn get(self) -> u16 {
self.0.get()
}
pub const fn to_raw(self) -> c_int {
self.0.get() as c_int
}
}
impl Display for UserId {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(&self.0, f)
}
}
impl From<NonZero<u16>> for UserId {
fn from(user_id: NonZero<u16>) -> Self {
Self(user_id)
}
}
impl From<UserId> for NonZero<u16> {
fn from(user_id: UserId) -> Self {
user_id.0
}
}
impl From<UserId> for u16 {
fn from(user_id: UserId) -> Self {
user_id.get()
}
}
impl From<UserId> for c_int {
fn from(user_id: UserId) -> Self {
user_id.to_raw()
}
}
impl TryFrom<c_int> for UserId {
type Error = InvalidUserId;
fn try_from(raw: c_int) -> Result<Self, Self::Error> {
Self::from_raw(raw)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct InvalidUserId(c_int);
impl InvalidUserId {
pub const fn is_sentinel(&self) -> bool {
matches!(self.0, -1 | 0)
}
}
impl Display for InvalidUserId {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
const C_INT_MIN: c_int = c_int::MIN;
match self.0 {
1..=RAW_USER_ID_MAX => unreachable!(),
-1 => write!(f, "No edict assigned"),
value @ (C_INT_MIN..-1 | 0) => {
hint::cold_path();
write!(f, "Invalid UserId representation: {value}")
}
value => write!(f, "Non-networkable UserId {value}"),
}
}
}
impl Error for InvalidUserId {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_conversion_accepts_only_non_zero_16_bit_values() {
assert_eq!(UserId::from_raw(-1), Err(InvalidUserId(-1)));
assert_eq!(UserId::from_raw(c_int::MIN), Err(InvalidUserId(c_int::MIN)));
assert_eq!(UserId::from_raw(0), Err(InvalidUserId(0)));
assert_eq!(UserId::from_raw(1).map(UserId::get), Ok(1));
assert_eq!(UserId::from_raw(65_535).map(UserId::get), Ok(u16::MAX));
assert_eq!(UserId::from_raw(65_536), Err(InvalidUserId(65_536)));
assert_eq!(UserId::new(0), None);
assert_eq!(UserId::new(7), UserId::from_raw(7).ok());
assert_eq!(UserId::try_from(0), Err(InvalidUserId(0)));
assert_eq!(UserId::try_from(-1), Err(InvalidUserId(-1)));
assert_eq!(
UserId::try_from(3),
Ok(UserId::from(NonZero::new(3).unwrap()))
);
assert_eq!(c_int::from(UserId::new(u16::MAX).unwrap()), 65_535);
assert_eq!(u16::from(UserId::new(7).unwrap()), 7);
assert_eq!(UserId::new(42).unwrap().to_raw(), 42);
assert_eq!(UserId::new(42).unwrap().to_string(), "42");
}
#[test]
fn invalid_user_ids_describe_why_they_are_invalid() {
assert!(InvalidUserId(0).is_sentinel());
assert!(InvalidUserId(-1).is_sentinel());
assert_eq!(InvalidUserId(-1).to_string(), "No edict assigned");
assert_eq!(
InvalidUserId(65_536).to_string(),
"Non-networkable UserId 65536"
);
assert_eq!(
InvalidUserId(c_int::MAX).to_string(),
format!("Non-networkable UserId {}", c_int::MAX)
);
assert_eq!(
InvalidUserId(0).to_string(),
"Invalid UserId representation: 0"
);
assert_eq!(
InvalidUserId(-2).to_string(),
"Invalid UserId representation: -2"
);
assert_eq!(
InvalidUserId(c_int::MIN).to_string(),
format!("Invalid UserId representation: {}", c_int::MIN)
);
}
}