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;