
# Objective Resolves #3824. `unsafe` code should be the exception, not the norm in Rust. It's obviously needed for various use cases as it's interfacing with platforms and essentially running the borrow checker at runtime in the ECS, but the touted benefits of Bevy is that we are able to heavily leverage Rust's safety, and we should be holding ourselves accountable to that by minimizing our unsafe footprint. ## Solution Deny `unsafe_code` workspace wide. Add explicit exceptions for the following crates, and forbid it in almost all of the others. * bevy_ecs - Obvious given how much unsafe is needed to achieve performant results * bevy_ptr - Works with raw pointers, even more low level than bevy_ecs. * bevy_render - due to needing to integrate with wgpu * bevy_window - due to needing to integrate with raw_window_handle * bevy_utils - Several unsafe utilities used by bevy_ecs. Ideally moved into bevy_ecs instead of made publicly usable. * bevy_reflect - Required for the unsafe type casting it's doing. * bevy_transform - for the parallel transform propagation * bevy_gizmos - For the SystemParam impls it has. * bevy_assets - To support reflection. Might not be required, not 100% sure yet. * bevy_mikktspace - due to being a conversion from a C library. Pending safe rewrite. * bevy_dynamic_plugin - Inherently unsafe due to the dynamic loading nature. Several uses of unsafe were rewritten, as they did not need to be using them: * bevy_text - a case of `Option::unchecked` could be rewritten as a normal for loop and match instead of an iterator. * bevy_color - the Pod/Zeroable implementations were replaceable with bytemuck's derive macros.
75 lines
3.9 KiB
Rust
75 lines
3.9 KiB
Rust
#![allow(unsafe_code)]
|
|
|
|
use bevy_ecs::prelude::Component;
|
|
use raw_window_handle::{
|
|
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
|
|
RawWindowHandle, WindowHandle,
|
|
};
|
|
|
|
/// A wrapper over [`RawWindowHandle`] and [`RawDisplayHandle`] that allows us to safely pass it across threads.
|
|
///
|
|
/// Depending on the platform, the underlying pointer-containing handle cannot be used on all threads,
|
|
/// and so we cannot simply make it (or any type that has a safe operation to get a [`RawWindowHandle`] or [`RawDisplayHandle`])
|
|
/// thread-safe.
|
|
#[derive(Debug, Clone, Component)]
|
|
pub struct RawHandleWrapper {
|
|
/// Raw handle to a window.
|
|
pub window_handle: RawWindowHandle,
|
|
/// Raw handle to the display server.
|
|
pub display_handle: RawDisplayHandle,
|
|
}
|
|
|
|
impl RawHandleWrapper {
|
|
/// Returns a [`HasWindowHandle`] + [`HasDisplayHandle`] impl, which exposes [`WindowHandle`] and [`DisplayHandle`].
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Some platforms have constraints on where/how this handle can be used. For example, some platforms don't support doing window
|
|
/// operations off of the main thread. The caller must ensure the [`RawHandleWrapper`] is only used in valid contexts.
|
|
pub unsafe fn get_handle(&self) -> ThreadLockedRawWindowHandleWrapper {
|
|
ThreadLockedRawWindowHandleWrapper(self.clone())
|
|
}
|
|
}
|
|
|
|
// SAFETY: [`RawHandleWrapper`] is just a normal "raw pointer", which doesn't impl Send/Sync. However the pointer is only
|
|
// exposed via an unsafe method that forces the user to make a call for a given platform. (ex: some platforms don't
|
|
// support doing window operations off of the main thread).
|
|
// A recommendation for this pattern (and more context) is available here:
|
|
// https://github.com/rust-windowing/raw-window-handle/issues/59
|
|
unsafe impl Send for RawHandleWrapper {}
|
|
// SAFETY: This is safe for the same reasons as the Send impl above.
|
|
unsafe impl Sync for RawHandleWrapper {}
|
|
|
|
/// A [`RawHandleWrapper`] that cannot be sent across threads.
|
|
///
|
|
/// This safely exposes [`RawWindowHandle`] and [`RawDisplayHandle`], but care must be taken to ensure that the construction itself is correct.
|
|
///
|
|
/// This can only be constructed via the [`RawHandleWrapper::get_handle()`] method;
|
|
/// be sure to read the safety docs there about platform-specific limitations.
|
|
/// In many cases, this should only be constructed on the main thread.
|
|
pub struct ThreadLockedRawWindowHandleWrapper(RawHandleWrapper);
|
|
|
|
impl HasWindowHandle for ThreadLockedRawWindowHandleWrapper {
|
|
fn window_handle(&self) -> Result<WindowHandle, HandleError> {
|
|
// SAFETY: the caller has validated that this is a valid context to get [`RawHandleWrapper`]
|
|
// as otherwise an instance of this type could not have been constructed
|
|
// NOTE: we cannot simply impl HasRawWindowHandle for RawHandleWrapper,
|
|
// as the `raw_window_handle` method is safe. We cannot guarantee that all calls
|
|
// of this method are correct (as it may be off the main thread on an incompatible platform),
|
|
// and so exposing a safe method to get a [`RawWindowHandle`] directly would be UB.
|
|
Ok(unsafe { WindowHandle::borrow_raw(self.0.window_handle) })
|
|
}
|
|
}
|
|
|
|
impl HasDisplayHandle for ThreadLockedRawWindowHandleWrapper {
|
|
fn display_handle(&self) -> Result<DisplayHandle, HandleError> {
|
|
// SAFETY: the caller has validated that this is a valid context to get [`RawDisplayHandle`]
|
|
// as otherwise an instance of this type could not have been constructed
|
|
// NOTE: we cannot simply impl HasRawDisplayHandle for RawHandleWrapper,
|
|
// as the `raw_display_handle` method is safe. We cannot guarantee that all calls
|
|
// of this method are correct (as it may be off the main thread on an incompatible platform),
|
|
// and so exposing a safe method to get a [`RawDisplayHandle`] directly would be UB.
|
|
Ok(unsafe { DisplayHandle::borrow_raw(self.0.display_handle) })
|
|
}
|
|
}
|