mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-14 03:58:18 +00:00
dcd76fd3e1
The glue is done using the [cxx crate](https://cxx.rs/) on the Rust side. As a proof-of-concept, only a small console command (`rust_version`) printing the currently used Rust version was added. You can generate and open the Rust documentation using `DDNET_TEST_NO_LINK=1 cargo doc --open`. You can run the Rust tests using `cmake --build <build dir> --target run_rust_tests`, they're automatically included in the `run_tests` target as well. Rust tests don't work on Windows in debug mode on Windows because Rust cannot currently link with the debug version of the C stdlib on Windows: https://github.com/rust-lang/rust/issues/39016. --- The stuff in `src/rust-bridge` is generated using ``` cxxbridge src/engine/shared/rust_version.rs --output src/rust-bridge/engine/shared/rust_version.cpp --output src/rust-bridge/engine/shared/rust_version.h cxxbridge src/engine/console.rs --output src/rust-bridge/cpp/console.cpp --output src/rust-bridge/cpp/console.h ```
269 lines
6.9 KiB
Rust
269 lines
6.9 KiB
Rust
use std::cmp;
|
|
use std::ffi::CStr;
|
|
use std::fmt;
|
|
use std::marker::PhantomData;
|
|
use std::ops;
|
|
use std::os::raw::c_char;
|
|
use std::ptr;
|
|
use std::str;
|
|
|
|
/// User pointer, as used in callbacks. Corresponds to the C++ type `void *`.
|
|
///
|
|
/// Callbacks in C are usually represented by a function pointer and some
|
|
/// "userdata" pointer that is also passed to the function pointer. This allows
|
|
/// to hand data to the callback. This type represents such a userdata poiner.
|
|
///
|
|
/// It is `unsafe` to convert the `UserPtr` back to its original pointer using
|
|
/// [`UserPtr::cast`] because its lifetime and type information was lost.
|
|
///
|
|
/// When dealing with Rust code exclusively, closures are preferred.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ddnet_base::UserPtr;
|
|
///
|
|
/// struct CallbackData {
|
|
/// favorite_color: &'static str,
|
|
/// }
|
|
///
|
|
/// let data = CallbackData {
|
|
/// favorite_color: "green",
|
|
/// };
|
|
///
|
|
/// callback(UserPtr::from(&data));
|
|
///
|
|
/// fn callback(pointer: UserPtr) {
|
|
/// let data: &CallbackData = unsafe { pointer.cast() };
|
|
/// println!("favorite color: {}", data.favorite_color);
|
|
/// }
|
|
/// ```
|
|
#[repr(transparent)]
|
|
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
|
|
pub struct UserPtr(*mut ());
|
|
|
|
unsafe impl cxx::ExternType for UserPtr {
|
|
type Id = cxx::type_id!("UserPtr");
|
|
type Kind = cxx::kind::Trivial;
|
|
}
|
|
|
|
impl UserPtr {
|
|
/// Create a null `UserPtr`.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ddnet_base::UserPtr;
|
|
///
|
|
/// // Can't do anything useful with this.
|
|
/// let _user = UserPtr::null();
|
|
/// ```
|
|
pub fn null() -> UserPtr {
|
|
UserPtr(ptr::null_mut())
|
|
}
|
|
|
|
/// Cast `UserPtr` back to a reference to its real type.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The caller is responsible for checking type and lifetime correctness.
|
|
/// Also, they must make sure that there are only immutable references or at
|
|
/// most one mutable reference live at the same time.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ddnet_base::UserPtr;
|
|
///
|
|
/// let the_answer = 42;
|
|
/// let user = UserPtr::from(&the_answer);
|
|
///
|
|
/// assert_eq!(unsafe { *user.cast::<i32>() }, 42);
|
|
/// ```
|
|
pub unsafe fn cast<T>(&self) -> &T {
|
|
&*(self.0 as *const _)
|
|
}
|
|
|
|
/// Cast `UserPtr` back to a mutable reference to its real type.
|
|
///
|
|
/// See [`UserPtr`] documentation for details and an example.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The caller is responsible for checking type and lifetime correctness.
|
|
/// Also, they must make sure that there are only immutable references or at
|
|
/// most one mutable reference live at the same time.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ddnet_base::UserPtr;
|
|
///
|
|
/// let mut seen_it = false;
|
|
/// let mut user = UserPtr::from(&mut seen_it);
|
|
///
|
|
/// unsafe {
|
|
/// *user.cast_mut() = true;
|
|
/// }
|
|
///
|
|
/// assert_eq!(seen_it, true);
|
|
/// ```
|
|
pub unsafe fn cast_mut<T>(&mut self) -> &mut T {
|
|
&mut *(self.0 as *mut _)
|
|
}
|
|
}
|
|
|
|
impl<'a, T> From<&'a T> for UserPtr {
|
|
fn from(t: &'a T) -> UserPtr {
|
|
UserPtr(t as *const _ as *mut _)
|
|
}
|
|
}
|
|
|
|
impl<'a, T> From<&'a mut T> for UserPtr {
|
|
fn from(t: &'a mut T) -> UserPtr {
|
|
UserPtr(t as *mut _ as *mut _)
|
|
}
|
|
}
|
|
|
|
/// C-style string pointer to UTF-8 data. Corresponds to the C++ type `const
|
|
/// char *`.
|
|
///
|
|
/// The lifetime is the lifetime of the underlying string.
|
|
///
|
|
/// This is a separate type from [`std::ffi::CStr`] because that type is not
|
|
/// FFI-safe and does not guarantee UTF-8.
|
|
///
|
|
/// In Rust code, [`String`] is preferred. For constructing C strings,
|
|
/// [`std::ffi::CString`] or this crate's [`s!`](`crate::s!`) macro can be used.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # fn some_c_function(_: StrRef<'_>) {}
|
|
/// use ddnet_base::StrRef;
|
|
/// use ddnet_base::s;
|
|
/// use std::ffi::CStr;
|
|
/// use std::ffi::CString;
|
|
/// use std::process;
|
|
///
|
|
/// some_c_function(CStr::from_bytes_with_nul(b"Hello!\0").unwrap().into());
|
|
///
|
|
/// let string = CString::new(format!("Current PID is {}.", process::id())).unwrap();
|
|
/// some_c_function(string.as_ref().into());
|
|
///
|
|
/// fn c_function_wrapper(s: &CStr) {
|
|
/// some_c_function(s.into());
|
|
/// }
|
|
///
|
|
/// some_c_function(s!("こんにちはC言語"));
|
|
/// ```
|
|
#[repr(transparent)]
|
|
#[derive(Eq)]
|
|
pub struct StrRef<'a>(*const c_char, PhantomData<&'a ()>);
|
|
|
|
unsafe impl<'a> cxx::ExternType for StrRef<'a> {
|
|
type Id = cxx::type_id!("StrRef");
|
|
type Kind = cxx::kind::Trivial;
|
|
}
|
|
|
|
impl<'a> StrRef<'a> {
|
|
/// Get the wrapped string reference.
|
|
///
|
|
/// This does the same as the `Deref` implementation, differing only in the
|
|
/// returned lifetime. `Deref`'s return type is bound by `self`'s lifetime,
|
|
/// this returns the more correct and longer lifetime.
|
|
///
|
|
/// This is an O(n) operation as it needs to calculate the length of a C
|
|
/// string by finding the first NUL byte.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ddnet_base::s;
|
|
///
|
|
/// let str1: &'static str = s!("static string").to_str();
|
|
/// ```
|
|
///
|
|
/// ```compile_fail
|
|
/// use ddnet_base::s;
|
|
///
|
|
/// // Wrong lifetime.
|
|
/// let str2: &'static str = &*s!("another static string");
|
|
/// ```
|
|
///
|
|
pub fn to_str(&self) -> &'a str {
|
|
unsafe { str::from_utf8_unchecked(CStr::from_ptr(self.0).to_bytes()) }
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a CStr> for StrRef<'a> {
|
|
fn from(s: &'a CStr) -> StrRef<'a> {
|
|
let bytes = s.to_bytes_with_nul();
|
|
str::from_utf8(bytes).expect("valid UTF-8");
|
|
StrRef(bytes.as_ptr() as *const _, PhantomData)
|
|
}
|
|
}
|
|
|
|
impl<'a> fmt::Debug for StrRef<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
self.to_str().fmt(f)
|
|
}
|
|
}
|
|
|
|
impl<'a> fmt::Display for StrRef<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
self.to_str().fmt(f)
|
|
}
|
|
}
|
|
|
|
impl<'a> cmp::PartialEq for StrRef<'a> {
|
|
fn eq(&self, other: &StrRef<'a>) -> bool {
|
|
self.to_str().eq(other.to_str())
|
|
}
|
|
}
|
|
|
|
impl<'a> cmp::PartialEq<&'a str> for StrRef<'a> {
|
|
fn eq(&self, other: &&'a str) -> bool {
|
|
self.to_str().eq(*other)
|
|
}
|
|
}
|
|
|
|
impl<'a> cmp::PartialOrd for StrRef<'a> {
|
|
fn partial_cmp(&self, other: &StrRef<'a>) -> Option<cmp::Ordering> {
|
|
self.to_str().partial_cmp(other.to_str())
|
|
}
|
|
}
|
|
|
|
impl<'a> cmp::Ord for StrRef<'a> {
|
|
fn cmp(&self, other: &StrRef<'a>) -> cmp::Ordering {
|
|
self.to_str().cmp(other.to_str())
|
|
}
|
|
}
|
|
|
|
impl<'a> ops::Deref for StrRef<'a> {
|
|
type Target = str;
|
|
fn deref(&self) -> &str {
|
|
self.to_str()
|
|
}
|
|
}
|
|
|
|
/// Construct a [`StrRef`] statically.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ddnet_base::StrRef;
|
|
/// use ddnet_base::s;
|
|
///
|
|
/// let greeting: StrRef<'static> = s!("Hallöchen, C!");
|
|
/// let status: StrRef<'static> = s!(concat!("Current file: ", file!()));
|
|
/// ```
|
|
#[macro_export]
|
|
macro_rules! s {
|
|
($str:expr) => {
|
|
::ddnet_base::StrRef::from(
|
|
::std::ffi::CStr::from_bytes_with_nul(::std::concat!($str, "\0").as_bytes()).unwrap(),
|
|
)
|
|
};
|
|
}
|