use super::server::ServerComponent;
use super::types::{SemanticVersion, StringView, UID};
use super::vtable::{call_vtable_small_struct, slots};
use std::ptr::NonNull;
pub trait OmpComponentHandle: Sized + Copy {
const UID: UID;
unsafe fn from_raw(ptr: NonNull<ServerComponent>) -> Self;
fn as_raw(&self) -> NonNull<ServerComponent>;
}
pub trait ComponentInterface {
const UID: UID;
const COMPONENT_OFFSET: isize = 0;
}
pub struct Component<I: ComponentInterface> {
ptr: NonNull<ServerComponent>,
interface: std::marker::PhantomData<*mut I>,
}
impl<I: ComponentInterface> Component<I> {
#[must_use]
pub fn as_ptr(&self) -> *mut I {
self.ptr
.as_ptr()
.cast::<u8>()
.wrapping_offset(-I::COMPONENT_OFFSET)
.cast::<I>()
}
}
impl<I: ComponentInterface> Clone for Component<I> {
fn clone(&self) -> Self {
*self
}
}
impl<I: ComponentInterface> Copy for Component<I> {}
impl<I: ComponentInterface> OmpComponentHandle for Component<I> {
const UID: UID = I::UID;
unsafe fn from_raw(ptr: NonNull<ServerComponent>) -> Self {
Self {
ptr,
interface: std::marker::PhantomData,
}
}
fn as_raw(&self) -> NonNull<ServerComponent> {
self.ptr
}
}
slots! {
SLOT_COMPONENT_NAME: usize = 7, 6;
}
slots! {
SLOT_COMPONENT_VERSION: usize = 9, 8;
}
pub fn component_name<T: OmpComponentHandle>(c: &T) -> Option<String> {
let raw = c.as_raw().as_ptr().cast::<u8>();
let view =
call_vtable_small_struct!(raw, 0, SLOT_COMPONENT_NAME, StringView, StringView::EMPTY)?;
unsafe { view.to_owned_string() }
}
pub fn component_version<T: OmpComponentHandle>(c: &T) -> Option<SemanticVersion> {
let raw = c.as_raw().as_ptr().cast::<u8>();
call_vtable_small_struct!(
raw,
0,
SLOT_COMPONENT_VERSION,
SemanticVersion,
SemanticVersion::new(0, 0, 0)
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::omp::vtable::MockTable;
use std::sync::Mutex;
static TEST_LOCK: Mutex<()> = Mutex::new(());
static MOCK_VTABLE: std::sync::OnceLock<MockTable<16>> = std::sync::OnceLock::new();
fn mock_vtable() -> &'static [*const (); 16] {
&MOCK_VTABLE
.get_or_init(|| {
let mut v = [unused as *const (); 16];
v[SLOT_COMPONENT_NAME] = mock_name as *const ();
v[SLOT_COMPONENT_VERSION] = mock_version as *const ();
MockTable(v)
})
.0
}
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn unused() {}
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn unused() {}
static MOCK_NAME_BYTES: &[u8] = b"test-comp";
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn mock_name(_this: *mut ServerComponent) -> StringView {
StringView {
data: MOCK_NAME_BYTES.as_ptr(),
len: MOCK_NAME_BYTES.len(),
}
}
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn mock_name(
_this: *mut ServerComponent,
out: *mut StringView,
) -> *mut StringView {
unsafe {
*out = StringView {
data: MOCK_NAME_BYTES.as_ptr(),
len: MOCK_NAME_BYTES.len(),
};
}
out
}
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn mock_version(_this: *mut ServerComponent) -> SemanticVersion {
SemanticVersion::new(2, 7, 3)
}
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn mock_version(
_this: *mut ServerComponent,
out: *mut SemanticVersion,
) -> *mut SemanticVersion {
unsafe {
*out = SemanticVersion::new(2, 7, 3);
}
out
}
#[derive(Debug, Clone, Copy)]
struct DummyComponent {
ptr: NonNull<ServerComponent>,
}
impl OmpComponentHandle for DummyComponent {
const UID: UID = 0xDEAD_BEEF_CAFE_BABE;
unsafe fn from_raw(ptr: NonNull<ServerComponent>) -> Self {
Self { ptr }
}
fn as_raw(&self) -> NonNull<ServerComponent> {
self.ptr
}
}
fn make_mock_component() -> *const *const () {
mock_vtable().as_ptr()
}
#[test]
fn component_slots_match_the_official_binaries() {
#[cfg(not(target_env = "msvc"))]
{
assert_eq!(SLOT_COMPONENT_NAME, 7);
assert_eq!(SLOT_COMPONENT_VERSION, 9);
}
#[cfg(target_env = "msvc")]
{
assert_eq!(SLOT_COMPONENT_NAME, 6);
assert_eq!(SLOT_COMPONENT_VERSION, 8);
}
}
#[test]
fn component_name_reads_slot_6_and_returns_string() {
let _g = TEST_LOCK.lock().unwrap();
let buf = make_mock_component();
let raw = (&raw const buf).cast::<ServerComponent>().cast_mut();
let nn = NonNull::new(raw).unwrap();
let comp = unsafe { DummyComponent::from_raw(nn) };
let name = component_name(&comp);
assert_eq!(name.as_deref(), Some("test-comp"));
}
#[test]
fn component_version_reads_slot_8_and_returns_semver() {
let _g = TEST_LOCK.lock().unwrap();
let buf = make_mock_component();
let raw = (&raw const buf).cast::<ServerComponent>().cast_mut();
let nn = NonNull::new(raw).unwrap();
let comp = unsafe { DummyComponent::from_raw(nn) };
let v = component_version(&comp).unwrap();
assert_eq!((v.major, v.minor, v.patch), (2, 7, 3));
}
#[test]
fn dummy_component_uid_is_consistent() {
assert_eq!(DummyComponent::UID, 0xDEAD_BEEF_CAFE_BABE);
}
}