use std::{ffi::c_void, marker::PhantomData, ptr::NonNull};
use crate::sys;
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RendererRenderStateGuardError {
MissingPlatformIo,
AlreadyOccupied,
Drift,
}
#[doc(hidden)]
pub struct RendererRenderStateGuard<'state, State> {
platform_io: NonNull<sys::ImGuiPlatformIO>,
expected: NonNull<State>,
_state: PhantomData<&'state mut State>,
}
impl<'state, State> RendererRenderStateGuard<'state, State> {
pub unsafe fn preflight(
platform_io: *mut sys::ImGuiPlatformIO,
) -> Result<(), RendererRenderStateGuardError> {
let platform_io =
NonNull::new(platform_io).ok_or(RendererRenderStateGuardError::MissingPlatformIo)?;
if unsafe { platform_io.as_ref().Renderer_RenderState }.is_null() {
Ok(())
} else {
Err(RendererRenderStateGuardError::AlreadyOccupied)
}
}
pub unsafe fn install(
platform_io: *mut sys::ImGuiPlatformIO,
state: &'state mut State,
) -> Result<Self, RendererRenderStateGuardError> {
let platform_io =
NonNull::new(platform_io).ok_or(RendererRenderStateGuardError::MissingPlatformIo)?;
unsafe { Self::preflight(platform_io.as_ptr()) }?;
let expected = NonNull::from(state);
unsafe {
(*platform_io.as_ptr()).Renderer_RenderState = expected.cast::<c_void>().as_ptr();
}
Ok(Self {
platform_io,
expected,
_state: PhantomData,
})
}
pub fn validate(&self) -> Result<(), RendererRenderStateGuardError> {
if unsafe { self.platform_io.as_ref().Renderer_RenderState }
== self.expected.cast::<c_void>().as_ptr()
{
Ok(())
} else {
Err(RendererRenderStateGuardError::Drift)
}
}
pub fn state_mut(&mut self) -> &mut State {
unsafe { self.expected.as_mut() }
}
pub fn finish(self) -> Result<(), RendererRenderStateGuardError> {
let result = self.validate();
if result.is_ok() {
unsafe {
(*self.platform_io.as_ptr()).Renderer_RenderState = std::ptr::null_mut();
}
}
result
}
}
impl<State> Drop for RendererRenderStateGuard<'_, State> {
fn drop(&mut self) {
if unsafe { self.platform_io.as_ref().Renderer_RenderState }
== self.expected.cast::<c_void>().as_ptr()
{
unsafe {
(*self.platform_io.as_ptr()).Renderer_RenderState = std::ptr::null_mut();
}
}
}
}
#[cfg(test)]
mod tests {
use super::{RendererRenderStateGuard, RendererRenderStateGuardError};
use crate::sys;
#[test]
fn guard_clears_its_own_state_on_drop() {
unsafe {
let platform_io = sys::ImGuiPlatformIO_ImGuiPlatformIO();
let mut state = 7_u8;
let expected = std::ptr::from_mut(&mut state).cast();
{
let _guard = RendererRenderStateGuard::install(platform_io, &mut state).unwrap();
assert_eq!((*platform_io).Renderer_RenderState, expected);
}
assert!((*platform_io).Renderer_RenderState.is_null());
sys::ImGuiPlatformIO_destroy(platform_io);
}
}
#[test]
fn guard_rejects_an_occupied_slot_without_mutation() {
unsafe {
let platform_io = sys::ImGuiPlatformIO_ImGuiPlatformIO();
let mut foreign = 1_u8;
let mut state = 2_u8;
(*platform_io).Renderer_RenderState = std::ptr::from_mut(&mut foreign).cast();
assert!(matches!(
RendererRenderStateGuard::install(platform_io, &mut state),
Err(RendererRenderStateGuardError::AlreadyOccupied)
));
assert_eq!(
(*platform_io).Renderer_RenderState,
std::ptr::from_mut(&mut foreign).cast()
);
sys::ImGuiPlatformIO_destroy(platform_io);
}
}
#[test]
fn guard_preserves_a_foreign_replacement_and_reports_drift() {
unsafe {
let platform_io = sys::ImGuiPlatformIO_ImGuiPlatformIO();
let mut state = 1_u8;
let mut foreign = 2_u8;
let guard = RendererRenderStateGuard::install(platform_io, &mut state).unwrap();
(*platform_io).Renderer_RenderState = std::ptr::from_mut(&mut foreign).cast();
assert_eq!(guard.finish(), Err(RendererRenderStateGuardError::Drift));
assert_eq!(
(*platform_io).Renderer_RenderState,
std::ptr::from_mut(&mut foreign).cast()
);
(*platform_io).Renderer_RenderState = std::ptr::null_mut();
sys::ImGuiPlatformIO_destroy(platform_io);
}
}
#[test]
fn guard_can_update_its_published_state_between_callbacks() {
unsafe {
let platform_io = sys::ImGuiPlatformIO_ImGuiPlatformIO();
let mut state = 1_u8;
let mut guard = RendererRenderStateGuard::install(platform_io, &mut state).unwrap();
*guard.state_mut() = 7;
assert_eq!(*((*platform_io).Renderer_RenderState.cast::<u8>()), 7);
guard.finish().unwrap();
assert_eq!(state, 7);
sys::ImGuiPlatformIO_destroy(platform_io);
}
}
#[test]
fn guard_clears_during_unwind() {
unsafe {
let platform_io = sys::ImGuiPlatformIO_ImGuiPlatformIO();
let mut state = 1_u8;
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _guard = RendererRenderStateGuard::install(platform_io, &mut state).unwrap();
panic!("injected renderer unwind");
}));
assert!(result.is_err());
assert!((*platform_io).Renderer_RenderState.is_null());
sys::ImGuiPlatformIO_destroy(platform_io);
}
}
}