memkit_bevy/lib.rs
1//! # memkit-bevy
2//!
3//! Bevy ECS integration for memkit.
4//!
5//! Provides automatic frame lifecycle management, resource integration, and
6//! optional GPU memory coordination for Bevy applications.
7//!
8//! ## Features
9//!
10//! - **Automatic frame lifecycle** — Frame begin/end tied to Bevy schedule
11//! - **Resource integration** — `MkAllocator` as a Bevy `Resource`
12//! - **System ordering** — Proper allocation timing via system sets
13//! - **Optional GPU support** — Enable `gpu` feature for memkit-co integration
14//! - **Prelude compatibility** — Enable `bevy_prelude` for full Bevy prelude
15//!
16//! ## Quick Start
17//!
18//! ```rust,ignore
19//! use bevy::prelude::*;
20//! use memkit_bevy::MkPlugin;
21//!
22//! fn main() {
23//! App::new()
24//! .add_plugins(DefaultPlugins)
25//! .add_plugins(MkPlugin::default())
26//! .add_systems(Update, my_system)
27//! .run();
28//! }
29//!
30//! fn my_system(alloc: Res<MkAllocatorRes>) {
31//! // Frame automatically managed - allocations valid until frame end
32//! let data = alloc.frame_alloc::<[f32; 4]>();
33//! // ...
34//! }
35//! ```
36//!
37//! ## Feature Flags
38//!
39//! - `bevy_prelude` — Enables full Bevy prelude re-exports
40//! - `gpu` — Enables GPU memory coordination via memkit-co
41
42use bevy_app::{App, Plugin, First, Last};
43use bevy_ecs::prelude::*;
44use memkit::{MkAllocator, MkConfig};
45
46#[cfg(feature = "gpu")]
47use memkit_co::bevy::BevyGpuCoordinator;
48#[cfg(feature = "gpu")]
49use memkit_gpu::DummyBackend;
50
51// Re-export bevy prelude if feature enabled
52#[cfg(feature = "bevy_prelude")]
53pub use bevy::prelude::*;
54
55// ============================================================================
56// System Sets
57// ============================================================================
58
59/// System set for memkit frame lifecycle.
60#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
61pub enum MkSystemSet {
62 /// Runs at the very start of each frame (calls `begin_frame`).
63 FrameBegin,
64 /// Runs at the very end of each frame (calls `end_frame`).
65 FrameEnd,
66}
67
68// ============================================================================
69// Resources
70// ============================================================================
71
72/// Bevy resource wrapping the memkit allocator.
73///
74/// Use this to access frame allocations in your systems:
75///
76/// ```rust,ignore
77/// fn my_system(alloc: Res<MkAllocatorRes>) {
78/// let data = alloc.frame_alloc::<MyStruct>();
79/// // data is valid until end of frame
80/// }
81/// ```
82#[derive(Resource)]
83pub struct MkAllocatorRes {
84 allocator: MkAllocator,
85 frame_active: bool,
86}
87
88impl MkAllocatorRes {
89 /// Create a new allocator resource with the given config.
90 pub fn new(config: MkConfig) -> Self {
91 Self {
92 allocator: MkAllocator::new(config),
93 frame_active: false,
94 }
95 }
96
97 /// Get a reference to the underlying allocator.
98 pub fn allocator(&self) -> &MkAllocator {
99 &self.allocator
100 }
101
102 /// Check if a frame is currently active.
103 pub fn is_frame_active(&self) -> bool {
104 self.frame_active
105 }
106
107 /// Allocate a value in the current frame's arena.
108 ///
109 /// Returns a raw pointer to uninitialized memory. You must initialize it.
110 ///
111 /// # Panics
112 /// Panics if called outside of an active frame.
113 pub fn frame_alloc<T>(&self) -> *mut T {
114 assert!(self.frame_active, "frame_alloc called outside active frame");
115 self.allocator.frame_alloc::<T>()
116 }
117
118 /// Allocate a slice in the current frame's arena.
119 ///
120 /// Returns an `MkFrameSlice` wrapper if successful.
121 pub fn frame_slice<T>(&self, len: usize) -> Option<memkit::MkFrameSlice<'_, T>> {
122 assert!(self.frame_active, "frame_slice called outside active frame");
123 self.allocator.frame_slice::<T>(len)
124 }
125
126 /// Allocate and initialize a value in the frame arena.
127 ///
128 /// Returns an `MkFrameBox` wrapper if successful.
129 pub fn frame_box<T>(&self, value: T) -> Option<memkit::MkFrameBox<'_, T>> {
130 assert!(self.frame_active, "frame_box called outside active frame");
131 self.allocator.frame_box(value)
132 }
133}
134
135impl std::ops::Deref for MkAllocatorRes {
136 type Target = MkAllocator;
137
138 fn deref(&self) -> &Self::Target {
139 &self.allocator
140 }
141}
142
143// ============================================================================
144// GPU Resource (optional)
145// ============================================================================
146
147#[cfg(feature = "gpu")]
148/// Bevy resource for GPU memory coordination.
149///
150/// This uses the thread-safe `BevyGpuCoordinator` from `memkit-co`.
151/// Only available when the `gpu` feature is enabled.
152///
153/// # Example
154///
155/// ```rust,ignore
156/// fn my_gpu_system(gpu: Res<MkGpuRes>) {
157/// let buffer = gpu.upload_slice(&[1.0f32, 2.0, 3.0]).unwrap();
158/// }
159/// ```
160#[derive(Resource)]
161pub struct MkGpuRes {
162 coordinator: BevyGpuCoordinator<DummyBackend>,
163}
164
165#[cfg(feature = "gpu")]
166impl MkGpuRes {
167 /// Create a new GPU resource.
168 pub fn new() -> Self {
169 Self {
170 coordinator: BevyGpuCoordinator::new(DummyBackend::new()),
171 }
172 }
173
174 /// Get a reference to the coordinator.
175 pub fn coordinator(&self) -> &BevyGpuCoordinator<DummyBackend> {
176 &self.coordinator
177 }
178}
179
180#[cfg(feature = "gpu")]
181impl Default for MkGpuRes {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187#[cfg(feature = "gpu")]
188impl std::ops::Deref for MkGpuRes {
189 type Target = BevyGpuCoordinator<DummyBackend>;
190
191 fn deref(&self) -> &Self::Target {
192 &self.coordinator
193 }
194}
195
196// ============================================================================
197// Plugin
198// ============================================================================
199
200/// Memkit plugin for Bevy.
201///
202/// Adds automatic frame lifecycle management to your Bevy app.
203/// Frame begin is called at the start of each frame (in `First`),
204/// and frame end is called at the end (in `Last`).
205///
206/// # Example
207///
208/// ```rust,ignore
209/// App::new()
210/// .add_plugins(DefaultPlugins)
211/// .add_plugins(MkPlugin::default())
212/// .run();
213/// ```
214pub struct MkPlugin {
215 config: MkConfig,
216 #[cfg(feature = "gpu")]
217 enable_gpu: bool,
218}
219
220impl Default for MkPlugin {
221 fn default() -> Self {
222 Self {
223 config: MkConfig::default(),
224 #[cfg(feature = "gpu")]
225 enable_gpu: true,
226 }
227 }
228}
229
230impl MkPlugin {
231 /// Create a new plugin with custom allocator configuration.
232 pub fn with_config(config: MkConfig) -> Self {
233 Self {
234 config,
235 #[cfg(feature = "gpu")]
236 enable_gpu: true,
237 }
238 }
239
240 /// Create a plugin with specific arena size.
241 pub fn with_arena_size(size: usize) -> Self {
242 Self::with_config(MkConfig {
243 frame_arena_size: size,
244 ..Default::default()
245 })
246 }
247
248 #[cfg(feature = "gpu")]
249 /// Disable GPU coordination even when the feature is enabled.
250 pub fn without_gpu(mut self) -> Self {
251 self.enable_gpu = false;
252 self
253 }
254}
255
256impl Plugin for MkPlugin {
257 fn build(&self, app: &mut App) {
258 // Insert allocator resource
259 app.insert_resource(MkAllocatorRes::new(self.config.clone()));
260
261 // Configure system sets
262 app.configure_sets(First, MkSystemSet::FrameBegin);
263 app.configure_sets(Last, MkSystemSet::FrameEnd);
264
265 // Add frame lifecycle systems
266 app.add_systems(First, frame_begin_system.in_set(MkSystemSet::FrameBegin));
267 app.add_systems(Last, frame_end_system.in_set(MkSystemSet::FrameEnd));
268
269 // Add GPU coordination if enabled
270 #[cfg(feature = "gpu")]
271 if self.enable_gpu {
272 app.insert_resource(MkGpuRes::new());
273 app.add_systems(First, gpu_frame_begin_system.after(MkSystemSet::FrameBegin));
274 app.add_systems(Last, gpu_frame_end_system.before(MkSystemSet::FrameEnd));
275 }
276 }
277}
278
279// ============================================================================
280// Systems
281// ============================================================================
282
283/// System that begins a new allocation frame.
284fn frame_begin_system(mut alloc: ResMut<MkAllocatorRes>) {
285 alloc.allocator.begin_frame();
286 alloc.frame_active = true;
287}
288
289/// System that ends the current allocation frame.
290fn frame_end_system(mut alloc: ResMut<MkAllocatorRes>) {
291 alloc.frame_active = false;
292 alloc.allocator.end_frame();
293}
294
295#[cfg(feature = "gpu")]
296/// System that begins GPU frame coordination.
297fn gpu_frame_begin_system(gpu: Res<MkGpuRes>) {
298 gpu.coordinator.begin_frame();
299}
300
301#[cfg(feature = "gpu")]
302/// System that ends GPU frame coordination.
303fn gpu_frame_end_system(gpu: Res<MkGpuRes>) {
304 let _ = gpu.coordinator.end_frame();
305}
306
307// ============================================================================
308// Convenience Exports
309// ============================================================================
310
311/// Prelude module for common imports.
312pub mod prelude {
313 pub use super::{MkPlugin, MkAllocatorRes, MkSystemSet};
314 pub use memkit::{MkConfig, MkFrameBox, MkFrameSlice};
315
316 #[cfg(feature = "gpu")]
317 pub use super::MkGpuRes;
318}
319
320// For backwards compatibility
321pub use MkAllocatorRes as MkAllocatorResource;