1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
use core::{any::TypeId, mem::MaybeUninit};
use std::ptr::{addr_of_mut, addr_of};
use crate::{never::Never, ValidAlignment, Alignment, align::AlignedBuffer};
fn never_type_id() -> TypeId {
core::any::TypeId::of::<Never>()
}
/// Core of a dungeon primitive.
///
/// Any static type with size less than or equal to `SIZE` bytes and alignment
/// requirements less than or equal to `2^ALIGN` bytes can be stored in the core.
///
/// By default, this type uses a `8` byte alignment as that
/// is the most common alignment for x64 machines.
pub struct DungeonCore<const SIZE: usize, const ALIGN: u8 = 3>
where
Alignment<ALIGN>: ValidAlignment,
{
/// The type of the value `data` contains.
///
/// If `current` is the TypeId of a uninhabited type
/// then `data` must not be used to make an instance of it.
current: TypeId,
/// A function to cause the value in `data` to be dropped.
drop_impl: fn(&mut DungeonCore<SIZE, ALIGN>),
/// Value of whatever type `current` says.
///
/// If `current` is a uninhabited type then `data` cannot be used
/// to make an instance of it.
buffer: AlignedBuffer<SIZE, ALIGN>,
}
impl<const SIZE: usize, const ALIGN: u8> core::fmt::Debug for DungeonCore<SIZE, ALIGN>
where
Alignment<ALIGN>: ValidAlignment,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DungeonCore")
.field("current_type", &if self.is_empty() { None } else { Some(self.current) })
.finish()
}
}
/// Drop the value inside a `DungeonCore` by using the `take` method.
/// Then immediately dropping the return. If the dungeon core was empty
/// then this does nothing.
fn drop_t<T, const SIZE: usize, const ALIGN: u8>(core: &mut DungeonCore<SIZE, ALIGN>)
where
T: 'static,
Alignment<ALIGN>: ValidAlignment,
{
let _ = core.take::<T>();
}
impl<const SIZE: usize, const ALIGN: u8> DungeonCore<SIZE, ALIGN>
where
Alignment<ALIGN>: ValidAlignment,
{
/// Create a empty dungeon core.
pub fn new() -> Self {
Self {
current: never_type_id(),
drop_impl: drop_t::<Never, SIZE, ALIGN>,
buffer: AlignedBuffer::new(),
}
}
/// Store a value of type `T`.
///
/// If `T` can not be stored by this dungeon core do to
/// size or alignment requirements a compiler error or
/// runtime panic will be generated. This behavior is
/// controlled by the `assert_runtime` feature.
///
/// If the dungeon core contains a value already it will
/// be dropped.
#[track_caller]
pub fn store<T: 'static>(&mut self, value: T) {
// Assert that this dungeon core can store a value of `T`.
crate::can_store::assert_can_store::<SIZE, ALIGN, T>();
// Drop the current value.
// Does nothing if already empty.
let drop_old = self.drop_impl;
drop_old(self);
// SAFETY:
// The call do `assert_can_store` makes the `store_unchecked` requirements
// satisfied if this code runs.
unsafe { self.store_unchecked::<T>(value) };
}
/// Gain ownership of the value inside the dungeon core.
///
/// This will leave the dungeon core empty if the value is of
/// type `T`. If the dungeon core is empty or has another type's
/// value stored in it then a `None` will be returned.
pub fn take<T: 'static>(&mut self) -> Option<T> {
if self.is_type::<T>() {
// SAFETY:
// We just checked that this dungeon core is storing a value
// of `T`.
Some(unsafe { self.take_unchecked::<T>() })
} else {
None
}
}
/// Replace the current value with a `T`.
///
/// The current value is returned if the dungeon core is not empty,
/// and the current stored value is of type `U`.
///
/// If `T` can not be stored by this dungeon core do to
/// size or alignment requirements a compiler error or
/// runtime panic will be generated. This behavior is
/// controlled by the `assert_runtime` feature.
///
/// This is slightly more efficient than a `take()` followed be
/// a `store()
#[track_caller]
pub fn replace<T: 'static, U: 'static>(&mut self, value: T) -> Option<U> {
// Assert `T` can be stored.
crate::can_store::assert_can_store::<SIZE, ALIGN, T>();
// Take the old value.
let old_value = self.take();
// SAFETY:
// The call to `assert_can_store()` satisfies the safety requirements.
//
// We also won't forget a value because we just took it.
unsafe { self.store_unchecked::<T>(value) };
old_value
}
/// Try to borrow the stored value.
///
/// If the dungeon core is empty or the stored value is
/// not of type `T` then `None` will be returned.
pub fn try_borrow<T: 'static>(&self) -> Option<&T> {
if !self.is_type::<T>() {
return None;
}
// SAFETY:
// We just checked that the current stored type is a `T`.
let value = unsafe { self.try_borrow_unchecked::<T>() };
Some(value)
}
/// Try to mutably borrow the stored value.
///
/// If the dungeon core is empty or the stored value is
/// not of type `T` then `None` will be returned.
pub fn try_borrow_mut<T: 'static>(&mut self) -> Option<&mut T> {
if !self.is_type::<T>() {
return None;
}
// SAFETY:
// We just checked that the current stored type is a `T`.
let value = unsafe { self.try_borrow_mut_unchecked::<T>() };
Some(value)
}
/// Check if a value is being stored.
pub fn is_empty(&self) -> bool {
self.current == never_type_id()
}
/// Check if a value being stored is of type `T`.
///
/// Will always return `false` if empty.
pub fn is_type<T: 'static>(&self) -> bool {
if self.is_empty() {
// A dungeon core never stores a value of `!`.
// It is used as a sentinel value.
false
} else {
self.current == core::any::TypeId::of::<T>()
}
}
}
/// Unsafe methods.
impl<const SIZE: usize, const ALIGN: u8> DungeonCore<SIZE, ALIGN>
where
Alignment<ALIGN>: ValidAlignment,
{
/// Unchecked form of `store`.
///
/// If the dungeon core is not empty, then the stored value will
/// be forgotten.
///
/// # Safety
/// `T` must be storable in `SIZE` bytes with alignment of `2^ALIGN` bytes.
pub unsafe fn store_unchecked<T: 'static>(&mut self, value: T) {
// Wrap the value so we can manually move it.
// `value` will be considered uninit after the read below.
let value = MaybeUninit::new(value);
// Get a pointer to the aligned buffer.
// This is done in long form to make it easier to read.
let data: &mut [MaybeUninit<u8>; SIZE] = &mut self.buffer.data;
let data: *mut [MaybeUninit<u8>; SIZE] = data as _;
let data: *mut MaybeUninit<T> = data.cast();
// SAFETY:
// `value` is valid for a read because we just made it above
// from a valid value.
//
// `data` is valid for a write because of the safety requirements
// for the outer function.
//
// They do not overlap.
unsafe {
core::ptr::copy_nonoverlapping(addr_of!(value), data, 1);
}
// Set the drop function to drop a value of `T`.
self.drop_impl = drop_t::<T, SIZE, ALIGN>;
// Set the current type to `T`.
self.current = core::any::TypeId::of::<T>();
}
/// Unchecked form of [take].
///
/// # Safety
/// The dungeon core must be storing a valid value of type `T`.
pub unsafe fn take_unchecked<T: 'static>(&mut self) -> T {
// Buffer to move the stored value into.
let mut value = MaybeUninit::uninit();
// Get a pointer to the aligned buffer.
// This is done in long form to make it easier to read.
let data: &[MaybeUninit<u8>; SIZE] = &self.buffer.data;
let data: *const [MaybeUninit<u8>; SIZE] = data as _;
let data: *const MaybeUninit<T> = data.cast();
// SAFETY:
// `data` is valid for a read because we borrowed it.
// And we must be storing a `T` because of the outer safety requirements.
//
// `value` is valid for a write because we just made it above.
//
// They do not overlap.
unsafe {
core::ptr::copy_nonoverlapping(data, addr_of_mut!(value), 1);
}
// Mark the dungeon core as empty.
self.current = never_type_id();
// SAFETY:
// The requirements of the outer function require that the
// value we just copied to be a valid `T`; And, by marking
// the dungeon core as empty the value will not be aliased.
unsafe { value.assume_init() }
}
/// Unchecked form of `try_borrow`.
///
/// # Safety
/// The dungeon core must be storing a valid value of type `T`.
pub unsafe fn try_borrow_unchecked<T: 'static>(&self) -> &T {
// Get a pointer to the aligned buffer.
// This is done in long form to make it easier to read.
let data: &[MaybeUninit<u8>; SIZE] = &self.buffer.data;
let data: *const [MaybeUninit<u8>; SIZE] = data as _;
let data: *const MaybeUninit<T> = data.cast();
// SAFETY:
// We just borrowed `data` so it's valid to dereference.
let data: &MaybeUninit<T> = unsafe { &*data };
// SAFETY:
// The outer safety requirements makes this safe.
// `data` is a valid value of `T`.
unsafe { data.assume_init_ref() }
}
/// Unchecked form of `try_borrow_mut`.
///
/// # Safety
/// The dungeon core must be storing a valid value of type `T`.
pub unsafe fn try_borrow_mut_unchecked<T: 'static>(&mut self) -> &mut T {
// Get a pointer to the aligned buffer.
// This is done in long form to make it easier to read.
let data: &mut [MaybeUninit<u8>; SIZE] = &mut self.buffer.data;
let data: *mut [MaybeUninit<u8>; SIZE] = data as _;
let data: *mut MaybeUninit<T> = data.cast();
// SAFETY:
// We just borrowed `data` so it's valid to dereference.
let data: &mut MaybeUninit<T> = unsafe { &mut *data };
// SAFETY:
// The outer safety requirements makes this safe.
// `data` is a valid value of `T`.
unsafe { data.assume_init_mut() }
}
}