raz_core/providers/
bevy.rs

1//! Bevy game engine provider for game development commands
2//!
3//! Provides intelligent command suggestions for Bevy game projects including
4//! development server, building for different platforms, and testing commands.
5
6use crate::{
7    Command, CommandBuilder, CommandCategory, CommandProvider, ProjectContext, ProjectType,
8    RazResult, SymbolKind,
9};
10use async_trait::async_trait;
11
12/// Provider for Bevy game engine commands
13pub struct BevyProvider {
14    priority: u8,
15}
16
17impl BevyProvider {
18    pub fn new() -> Self {
19        Self {
20            priority: 85, // High priority for Bevy game projects
21        }
22    }
23}
24
25#[async_trait]
26impl CommandProvider for BevyProvider {
27    fn name(&self) -> &str {
28        "bevy"
29    }
30
31    fn priority(&self) -> u8 {
32        self.priority
33    }
34
35    fn can_handle(&self, context: &ProjectContext) -> bool {
36        // Check if this is a Bevy project
37        match &context.project_type {
38            ProjectType::Bevy => true,
39            ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Bevy),
40            _ => {
41                // Also check dependencies for bevy
42                context
43                    .dependencies
44                    .iter()
45                    .any(|dep| dep.name == "bevy" || dep.name.starts_with("bevy_"))
46            }
47        }
48    }
49
50    async fn commands(&self, context: &ProjectContext) -> RazResult<Vec<Command>> {
51        let mut commands = Vec::new();
52
53        // Development commands
54        commands.extend(self.development_commands(context));
55
56        // Build commands for different platforms
57        commands.extend(self.build_commands(context));
58
59        // Testing commands
60        commands.extend(self.test_commands(context));
61
62        // Context-aware commands
63        commands.extend(self.context_aware_commands(context));
64
65        // Asset and optimization commands
66        commands.extend(self.asset_commands(context));
67
68        Ok(commands)
69    }
70}
71
72impl BevyProvider {
73    /// Development and running commands
74    fn development_commands(&self, _context: &ProjectContext) -> Vec<Command> {
75        vec![
76            CommandBuilder::new("bevy-run", "cargo")
77                .label("Run Bevy Game")
78                .description("Run the Bevy game in development mode")
79                .arg("run")
80                .category(CommandCategory::Run)
81                .priority(95)
82                .tag("dev")
83                .tag("run")
84                .tag("bevy")
85                .estimated_duration(3)
86                .build(),
87            CommandBuilder::new("bevy-run-release", "cargo")
88                .label("Run Release Build")
89                .description("Run optimized release build of the game")
90                .arg("run")
91                .arg("--release")
92                .category(CommandCategory::Run)
93                .priority(85)
94                .tag("run")
95                .tag("release")
96                .tag("bevy")
97                .estimated_duration(5)
98                .build(),
99            CommandBuilder::new("bevy-run-features", "cargo")
100                .label("Run with Dynamic Features")
101                .description("Run with dynamic linking for faster iteration")
102                .arg("run")
103                .arg("--features")
104                .arg("bevy/dynamic_linking")
105                .category(CommandCategory::Run)
106                .priority(88)
107                .tag("dev")
108                .tag("dynamic")
109                .tag("bevy")
110                .estimated_duration(4)
111                .build(),
112            CommandBuilder::new("bevy-check", "cargo")
113                .label("Check Bevy Code")
114                .description("Fast compile check without building")
115                .arg("check")
116                .category(CommandCategory::Lint)
117                .priority(90)
118                .tag("check")
119                .tag("fast")
120                .tag("bevy")
121                .estimated_duration(8)
122                .build(),
123        ]
124    }
125
126    /// Build commands for different platforms and targets
127    fn build_commands(&self, context: &ProjectContext) -> Vec<Command> {
128        let mut commands = vec![
129            CommandBuilder::new("bevy-build", "cargo")
130                .label("Build Bevy Game")
131                .description("Build the game for the current platform")
132                .arg("build")
133                .category(CommandCategory::Build)
134                .priority(80)
135                .tag("build")
136                .tag("bevy")
137                .estimated_duration(45)
138                .build(),
139            CommandBuilder::new("bevy-build-release", "cargo")
140                .label("Build Release")
141                .description("Build optimized release version")
142                .arg("build")
143                .arg("--release")
144                .category(CommandCategory::Build)
145                .priority(85)
146                .tag("build")
147                .tag("release")
148                .tag("bevy")
149                .estimated_duration(90)
150                .build(),
151        ];
152
153        // Add platform-specific builds if we detect cross-compilation setup
154        if context.dependencies.iter().any(|d| d.name.contains("wasm")) {
155            commands.push(
156                CommandBuilder::new("bevy-build-wasm", "cargo")
157                    .label("Build for WASM")
158                    .description("Build Bevy game for web (WebAssembly)")
159                    .arg("build")
160                    .arg("--target")
161                    .arg("wasm32-unknown-unknown")
162                    .arg("--release")
163                    .category(CommandCategory::Build)
164                    .priority(75)
165                    .tag("build")
166                    .tag("wasm")
167                    .tag("web")
168                    .tag("bevy")
169                    .estimated_duration(120)
170                    .build(),
171            );
172        }
173
174        // Windows cross-compilation
175        commands.push(
176            CommandBuilder::new("bevy-build-windows", "cargo")
177                .label("Build for Windows")
178                .description("Cross-compile for Windows")
179                .arg("build")
180                .arg("--target")
181                .arg("x86_64-pc-windows-gnu")
182                .arg("--release")
183                .category(CommandCategory::Build)
184                .priority(70)
185                .tag("build")
186                .tag("windows")
187                .tag("cross")
188                .tag("bevy")
189                .estimated_duration(100)
190                .build(),
191        );
192
193        commands
194    }
195
196    /// Testing commands
197    fn test_commands(&self, _context: &ProjectContext) -> Vec<Command> {
198        vec![
199            CommandBuilder::new("bevy-test", "cargo")
200                .label("Run Bevy Tests")
201                .description("Run unit and integration tests")
202                .arg("test")
203                .category(CommandCategory::Test)
204                .priority(75)
205                .tag("test")
206                .tag("bevy")
207                .estimated_duration(20)
208                .build(),
209            CommandBuilder::new("bevy-test-headless", "cargo")
210                .label("Headless Tests")
211                .description("Run tests without rendering (headless)")
212                .arg("test")
213                .arg("--features")
214                .arg("bevy/headless")
215                .category(CommandCategory::Test)
216                .priority(80)
217                .tag("test")
218                .tag("headless")
219                .tag("bevy")
220                .estimated_duration(15)
221                .build(),
222            CommandBuilder::new("bevy-test-doc", "cargo")
223                .label("Test Documentation")
224                .description("Test code examples in documentation")
225                .arg("test")
226                .arg("--doc")
227                .category(CommandCategory::Test)
228                .priority(65)
229                .tag("test")
230                .tag("doc")
231                .tag("bevy")
232                .estimated_duration(25)
233                .build(),
234        ]
235    }
236
237    /// Context-aware commands based on cursor position
238    fn context_aware_commands(&self, context: &ProjectContext) -> Vec<Command> {
239        let mut commands = Vec::new();
240
241        if let Some(file_context) = &context.current_file {
242            if let Some(symbol) = &file_context.cursor_symbol {
243                match symbol.kind {
244                    SymbolKind::Function => {
245                        // System-related commands for functions that look like Bevy systems
246                        if symbol.name.ends_with("_system")
247                            || symbol.name.contains("update")
248                            || symbol.name.contains("spawn")
249                        {
250                            commands.push(
251                                CommandBuilder::new("bevy-run-system", "cargo")
252                                    .label("Run with System Focus")
253                                    .description(format!(
254                                        "Run game focusing on system: {}",
255                                        symbol.name
256                                    ))
257                                    .arg("run")
258                                    .arg("--features")
259                                    .arg("bevy/trace")
260                                    .category(CommandCategory::Run)
261                                    .priority(85)
262                                    .tag("system")
263                                    .tag("debug")
264                                    .tag("bevy")
265                                    .estimated_duration(8)
266                                    .build(),
267                            );
268                        }
269
270                        // Test function commands
271                        if symbol.name.starts_with("test_")
272                            || symbol.modifiers.contains(&"test".to_string())
273                        {
274                            commands.push(
275                                CommandBuilder::new("bevy-test-current", "cargo")
276                                    .label("Test Current System")
277                                    .description(format!("Run test: {}", symbol.name))
278                                    .arg("test")
279                                    .arg(&symbol.name)
280                                    .arg("--")
281                                    .arg("--nocapture")
282                                    .category(CommandCategory::Test)
283                                    .priority(90)
284                                    .tag("test")
285                                    .tag("current")
286                                    .tag("bevy")
287                                    .estimated_duration(5)
288                                    .build(),
289                            );
290                        }
291                    }
292                    SymbolKind::Struct => {
293                        // Component-related commands
294                        if symbol.name.ends_with("Component")
295                            || symbol.modifiers.contains(&"Component".to_string())
296                        {
297                            commands.push(
298                                CommandBuilder::new("bevy-inspect-component", "cargo")
299                                    .label("Inspect Component")
300                                    .description(format!(
301                                        "Run with component inspector: {}",
302                                        symbol.name
303                                    ))
304                                    .arg("run")
305                                    .arg("--features")
306                                    .arg("bevy-inspector-egui/default")
307                                    .category(CommandCategory::Run)
308                                    .priority(80)
309                                    .tag("component")
310                                    .tag("inspector")
311                                    .tag("bevy")
312                                    .estimated_duration(10)
313                                    .build(),
314                            );
315                        }
316
317                        // Resource-related commands
318                        if symbol.name.ends_with("Resource") {
319                            commands.push(
320                                CommandBuilder::new("bevy-debug-resource", "cargo")
321                                    .label("Debug Resource")
322                                    .description(format!(
323                                        "Run with resource debugging: {}",
324                                        symbol.name
325                                    ))
326                                    .arg("run")
327                                    .arg("--features")
328                                    .arg("bevy/trace")
329                                    .category(CommandCategory::Run)
330                                    .priority(75)
331                                    .tag("resource")
332                                    .tag("debug")
333                                    .tag("bevy")
334                                    .estimated_duration(8)
335                                    .build(),
336                            );
337                        }
338                    }
339                    _ => {}
340                }
341            }
342
343            // File-based commands
344            if file_context.path.to_string_lossy().contains("system") {
345                commands.push(
346                    CommandBuilder::new("bevy-check-systems", "cargo")
347                        .label("Check Systems")
348                        .description("Check all game systems for compilation errors")
349                        .arg("check")
350                        .arg("--features")
351                        .arg("bevy/trace")
352                        .category(CommandCategory::Lint)
353                        .priority(80)
354                        .tag("systems")
355                        .tag("check")
356                        .tag("bevy")
357                        .estimated_duration(12)
358                        .build(),
359                );
360            }
361
362            if file_context.path.to_string_lossy().contains("asset") {
363                commands.push(
364                    CommandBuilder::new("bevy-check-assets", "cargo")
365                        .label("Validate Assets")
366                        .description("Check asset loading and validation")
367                        .arg("run")
368                        .arg("--features")
369                        .arg("bevy/filesystem_watcher")
370                        .category(CommandCategory::Run)
371                        .priority(75)
372                        .tag("assets")
373                        .tag("validate")
374                        .tag("bevy")
375                        .estimated_duration(15)
376                        .build(),
377                );
378            }
379        }
380
381        commands
382    }
383
384    /// Asset management and optimization commands
385    fn asset_commands(&self, _context: &ProjectContext) -> Vec<Command> {
386        vec![
387            CommandBuilder::new("bevy-assets-watch", "cargo")
388                .label("Watch Assets")
389                .description("Run with asset hot-reloading enabled")
390                .arg("run")
391                .arg("--features")
392                .arg("bevy/file_watcher")
393                .category(CommandCategory::Run)
394                .priority(85)
395                .tag("assets")
396                .tag("watch")
397                .tag("bevy")
398                .estimated_duration(5)
399                .build(),
400            CommandBuilder::new("bevy-optimize-assets", "cargo")
401                .label("Optimize Assets")
402                .description("Build with optimized asset processing")
403                .arg("build")
404                .arg("--release")
405                .arg("--features")
406                .arg("bevy/serialize")
407                .category(CommandCategory::Build)
408                .priority(70)
409                .tag("assets")
410                .tag("optimize")
411                .tag("bevy")
412                .estimated_duration(60)
413                .build(),
414            CommandBuilder::new("bevy-bundle-assets", "cargo")
415                .label("Bundle Assets")
416                .description("Create asset bundle for distribution")
417                .arg("build")
418                .arg("--release")
419                .arg("--features")
420                .arg("bevy/embedded_watcher")
421                .category(CommandCategory::Deploy)
422                .priority(65)
423                .tag("bundle")
424                .tag("assets")
425                .tag("deploy")
426                .tag("bevy")
427                .estimated_duration(90)
428                .build(),
429        ]
430    }
431}
432
433impl Default for BevyProvider {
434    fn default() -> Self {
435        Self::new()
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442    use crate::{BuildTarget, Dependency, ProjectType, TargetType, WorkspaceMember};
443    use std::collections::HashMap;
444    use std::path::PathBuf;
445
446    fn create_bevy_context() -> ProjectContext {
447        ProjectContext {
448            workspace_root: PathBuf::from("/test"),
449            current_file: None,
450            cursor_position: None,
451            project_type: ProjectType::Bevy,
452            dependencies: vec![Dependency {
453                name: "bevy".to_string(),
454                version: "0.12".to_string(),
455                features: vec!["dynamic_linking".to_string()],
456                optional: false,
457                dev_dependency: false,
458            }],
459            workspace_members: vec![WorkspaceMember {
460                name: "bevy-game".to_string(),
461                path: PathBuf::from("/test"),
462                package_type: ProjectType::Bevy,
463            }],
464            build_targets: vec![BuildTarget {
465                name: "main".to_string(),
466                target_type: TargetType::Binary,
467                path: PathBuf::from("/test/src/main.rs"),
468            }],
469            active_features: vec!["dynamic_linking".to_string()],
470            env_vars: HashMap::new(),
471        }
472    }
473
474    #[tokio::test]
475    async fn test_bevy_provider_can_handle() {
476        let provider = BevyProvider::new();
477        let context = create_bevy_context();
478
479        assert!(provider.can_handle(&context));
480        assert_eq!(provider.name(), "bevy");
481        assert_eq!(provider.priority(), 85);
482    }
483
484    #[tokio::test]
485    async fn test_bevy_commands_generation() {
486        let provider = BevyProvider::new();
487        let context = create_bevy_context();
488
489        let commands = provider.commands(&context).await.unwrap();
490
491        assert!(!commands.is_empty());
492
493        // Should have development commands
494        assert!(commands.iter().any(|c| c.id == "bevy-run"));
495        assert!(commands.iter().any(|c| c.id == "bevy-run-features"));
496
497        // Should have build commands
498        assert!(commands.iter().any(|c| c.id == "bevy-build"));
499        assert!(commands.iter().any(|c| c.id == "bevy-build-release"));
500
501        // Should have test commands
502        assert!(commands.iter().any(|c| c.id == "bevy-test"));
503        assert!(commands.iter().any(|c| c.id == "bevy-test-headless"));
504
505        // Should have asset commands
506        assert!(commands.iter().any(|c| c.id == "bevy-assets-watch"));
507    }
508
509    #[tokio::test]
510    async fn test_bevy_wasm_commands() {
511        let provider = BevyProvider::new();
512        let mut context = create_bevy_context();
513
514        // Add wasm dependency
515        context.dependencies.push(Dependency {
516            name: "wasm-bindgen".to_string(),
517            version: "0.2".to_string(),
518            features: vec![],
519            optional: false,
520            dev_dependency: false,
521        });
522
523        let commands = provider.commands(&context).await.unwrap();
524
525        // Should have WASM build command
526        assert!(commands.iter().any(|c| c.id == "bevy-build-wasm"));
527    }
528
529    #[tokio::test]
530    async fn test_bevy_command_priorities() {
531        let provider = BevyProvider::new();
532        let context = create_bevy_context();
533
534        let commands = provider.commands(&context).await.unwrap();
535
536        // Run command should have highest priority
537        let run_cmd = commands.iter().find(|c| c.id == "bevy-run").unwrap();
538        assert_eq!(run_cmd.priority, 95);
539
540        // All commands should have bevy tag
541        assert!(
542            commands
543                .iter()
544                .all(|c| c.tags.contains(&"bevy".to_string()))
545        );
546    }
547
548    #[tokio::test]
549    async fn test_bevy_dependency_detection() {
550        let provider = BevyProvider::new();
551        let mut context = create_bevy_context();
552        context.project_type = ProjectType::Binary; // Not explicitly Game
553        // But has bevy dependency (set in create_bevy_context)
554
555        assert!(provider.can_handle(&context));
556    }
557}