fromsoftware_shared/game_allocator.rs
1use std::{alloc::Layout, ptr::NonNull};
2
3use thiserror::Error;
4
5/// An error indicating that an allocation failed for any reason, including
6/// memory exhaustion or because the requested layout didn't meet the
7/// allocator's alignment requirements.
8#[derive(Error, Debug)]
9#[error("Allocation failed")]
10pub struct AllocError;
11
12/// A trait for global allocators within a game that are able to deallocate any
13/// memory on the game's main heap. Generally corresponds to the drop logic for
14/// `DLUT::DLAutoDeletePtr` in games from _Bloodborne_ forward.
15///
16/// The API for this generally matches [`std::alloc::Allocator`] where possible.
17/// However, it diverges in certain places that are necessary for compatibility
18/// with the games' memory management.
19///
20/// ### Allocator compatibility
21///
22/// An allocator is *compatible with* this allocator if memory it allocates can
23/// be passed to [`deallocate`] without causing undefined behavior. A given
24/// [`GameAllocator]` must be compatible with itself, but other allocators may
25/// also be compatible. For example, the `GameAllocator` may track global
26/// allocation arenas and determine which global allocator to use to deallocate
27/// a given pointer based on where it appears in those arenas.
28///
29/// ### Currently allocated memory
30///
31/// Some of the methods require that a memory block is *currently allocated* by
32/// an allocator. This means that:
33///
34/// * the starting address for that memory block was previously returned by
35/// [`allocate`] *or* by another allocator that's known to be [*compatible
36/// with*] this one, and
37/// * the memory block has not subsequently been deallocated.
38///
39/// A memory block is deallocated by a call to [`deallocate`].
40///
41/// [`allocate`]: Self::allocate
42/// [*compatible with*]: #allocator-compatibility
43/// [`deallocate`]: Self::deallocate
44///
45/// ### Memory fitting
46///
47/// Some of the methods require that a `layout` *fit* a memory block or vice
48/// versa. This means that the following conditions must hold:
49///
50/// * the memory block must be *currently allocated* with alignment of
51/// [`layout.align()`], and
52///
53/// * [`layout.size()`] must fall in the range `min ..= max`, where:
54/// - `min` is the size of the layout used to allocate the block, and
55/// - `max` is the actual size returned from [`allocate`].
56///
57/// [`layout.align()`]: Layout::align
58/// [`layout.size()`]: Layout::size
59pub trait GameAllocator {
60 /// Attempts to allocate a block of memory.
61 ///
62 /// On success, returns a `NonNull<[u8]>` meeting the size and alignment
63 /// guarantees of `layout`. The returned block may have a larger size than
64 /// specified by `layout.size()`, and may or may not have its contents
65 /// initialized.
66 ///
67 /// The returned block of memory remains valid until it's passed to
68 /// [`deallocate`].
69 ///
70 /// [`deallocate`]: Self::deallocate
71 ///
72 /// **Note:** Unlike [std::alloc::Allocator], this does not take a reference
73 /// to `&self`. This implies that any state involved in allocation must be
74 /// global, thread-safe, and must be valid for the duration of execution.
75 ///
76 /// ## Errors
77 ///
78 /// Unlike [std::alloc::Allocator], this may return an error for reasons
79 /// other than the memory being exhausted or `layout` not meeting the
80 /// allocator's size or alignment constraints.
81 ///
82 /// Clients wishing to abort computation in response to an allocation error
83 /// are encouraged to call the [`handle_alloc_error`] function, rather than
84 /// directly invoking [`panic!`] or similar.
85 ///
86 /// [`handle_alloc_error`]: std::alloc::handle_alloc_error
87 fn allocate(layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
88
89 /// Deallocates the memory referenced by `ptr`.
90 ///
91 /// # Safety
92 ///
93 /// * `ptr` must denote a block of memory [*currently allocated*] via this
94 /// allocator, and
95 /// * `layout` must [*fit*] that block of memory.
96 ///
97 /// [*currently allocated*]: #currently-allocated-memory
98 /// [*fit*]: #memory-fitting
99 unsafe fn deallocate(ptr: NonNull<u8>, layout: Layout);
100}
101
102/// A [`GameAllocator`] that never actually allocates or drops memory.
103///
104/// [`allocate`] always returns [`AllocError`] and [`deallocate`] always panics.
105///
106/// [`allocate`]: Self::allocate
107/// [`deallocate`]: Self::deallocate
108///
109/// This is intended to by used as the allocator for types such as [`OwnedPtr`]
110/// that represent memory allocated by the game that *don't* use the game's main
111/// heap and so whose deallocation behavior is unknown. It's always incorrect to
112/// try to create or destroy such types in Rust code.
113///
114/// [`OwnedPtr`]: crate::OwnedPtr
115///
116/// Because [`NoOpAllocator::deallocate`] never has undefined behavior for any
117/// memory blocks, all allocators are technically *compatible with* it.
118pub struct NoOpAllocator;
119
120impl GameAllocator for NoOpAllocator {
121 /// Always returns [`AllocError`].
122 fn allocate(_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
123 Err(AllocError)
124 }
125
126 /// Always panics.
127 unsafe fn deallocate(_ptr: NonNull<u8>, _layout: Layout) {
128 panic!("Can't drop data with NoOpAllocator");
129 }
130}