terrain_forge/
pipeline.rs

1//! Pipeline intelligence system for conditional generation
2//!
3//! Provides conditional operations, parameter passing, and template systems
4//! for intelligent pipeline composition and control flow.
5
6use crate::{Grid, Rng, Tile};
7use std::collections::HashMap;
8
9/// Conditions that can be evaluated during pipeline execution
10#[derive(Debug, Clone)]
11pub enum PipelineCondition {
12    /// Check if floor tile count meets threshold
13    FloorCount {
14        min: Option<usize>,
15        max: Option<usize>,
16    },
17    /// Check if region count meets threshold  
18    RegionCount {
19        min: Option<usize>,
20        max: Option<usize>,
21    },
22    /// Check if grid density (floor/total ratio) meets threshold
23    Density { min: Option<f32>, max: Option<f32> },
24    /// Check if connectivity meets requirements
25    Connected { required: bool },
26    /// Custom condition with user-provided function
27    Custom(fn(&Grid<Tile>, &PipelineContext) -> bool),
28}
29
30impl PipelineCondition {
31    /// Evaluate condition against current grid and context
32    pub fn evaluate(&self, grid: &Grid<Tile>, context: &PipelineContext) -> bool {
33        match self {
34            PipelineCondition::FloorCount { min, max } => {
35                let count = grid.count(|t| t.is_floor());
36                if let Some(min_val) = min {
37                    if count < *min_val {
38                        return false;
39                    }
40                }
41                if let Some(max_val) = max {
42                    if count > *max_val {
43                        return false;
44                    }
45                }
46                true
47            }
48            PipelineCondition::RegionCount { min, max } => {
49                let count = context
50                    .get_parameter("region_count")
51                    .and_then(|v| v.parse::<usize>().ok())
52                    .unwrap_or(0);
53                if let Some(min_val) = min {
54                    if count < *min_val {
55                        return false;
56                    }
57                }
58                if let Some(max_val) = max {
59                    if count > *max_val {
60                        return false;
61                    }
62                }
63                true
64            }
65            PipelineCondition::Density { min, max } => {
66                let total = grid.width() * grid.height();
67                let floors = grid.count(|t| t.is_floor());
68                let density = floors as f32 / total as f32;
69                if let Some(min_val) = min {
70                    if density < *min_val {
71                        return false;
72                    }
73                }
74                if let Some(max_val) = max {
75                    if density > *max_val {
76                        return false;
77                    }
78                }
79                true
80            }
81            PipelineCondition::Connected { required } => {
82                // Simple connectivity check - assume connected if we have floors
83                let has_floors = grid.count(|t| t.is_floor()) > 0;
84                has_floors == *required
85            }
86            PipelineCondition::Custom(func) => func(grid, context),
87        }
88    }
89}
90
91/// Context for passing data between pipeline stages
92#[derive(Debug, Clone)]
93pub struct PipelineContext {
94    /// Key-value parameters passed between stages
95    parameters: HashMap<String, String>,
96    /// Stage execution history
97    execution_log: Vec<String>,
98    /// Current iteration count for loops
99    iteration_count: usize,
100}
101
102impl PipelineContext {
103    /// Create new empty context
104    pub fn new() -> Self {
105        Self {
106            parameters: HashMap::new(),
107            execution_log: Vec::new(),
108            iteration_count: 0,
109        }
110    }
111
112    /// Set a parameter value
113    pub fn set_parameter(&mut self, key: impl Into<String>, value: impl Into<String>) {
114        self.parameters.insert(key.into(), value.into());
115    }
116
117    /// Get a parameter value
118    pub fn get_parameter(&self, key: &str) -> Option<&String> {
119        self.parameters.get(key)
120    }
121
122    /// Log stage execution
123    pub fn log_execution(&mut self, stage: impl Into<String>) {
124        self.execution_log.push(stage.into());
125    }
126
127    /// Get execution history
128    pub fn execution_history(&self) -> &[String] {
129        &self.execution_log
130    }
131
132    /// Increment iteration counter
133    pub fn increment_iteration(&mut self) {
134        self.iteration_count += 1;
135    }
136
137    /// Get current iteration count
138    pub fn iteration_count(&self) -> usize {
139        self.iteration_count
140    }
141}
142
143impl Default for PipelineContext {
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149/// Result from a pipeline stage execution
150#[derive(Debug, Clone)]
151pub struct StageResult {
152    /// Whether the stage succeeded
153    pub success: bool,
154    /// Optional message about execution
155    pub message: Option<String>,
156    /// Parameters to pass to next stage
157    pub output_parameters: HashMap<String, String>,
158}
159
160impl StageResult {
161    /// Create successful result
162    pub fn success() -> Self {
163        Self {
164            success: true,
165            message: None,
166            output_parameters: HashMap::new(),
167        }
168    }
169
170    /// Create successful result with message
171    pub fn success_with_message(message: impl Into<String>) -> Self {
172        Self {
173            success: true,
174            message: Some(message.into()),
175            output_parameters: HashMap::new(),
176        }
177    }
178
179    /// Create failed result
180    pub fn failure(message: impl Into<String>) -> Self {
181        Self {
182            success: false,
183            message: Some(message.into()),
184            output_parameters: HashMap::new(),
185        }
186    }
187
188    /// Add output parameter
189    pub fn with_parameter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
190        self.output_parameters.insert(key.into(), value.into());
191        self
192    }
193}
194
195/// Map for aggregating parameters from multiple pipeline branches
196#[derive(Debug, Clone)]
197pub struct ParameterMap {
198    /// Parameters from different branches
199    branch_parameters: HashMap<String, HashMap<String, String>>,
200}
201
202impl ParameterMap {
203    /// Create new parameter map
204    pub fn new() -> Self {
205        Self {
206            branch_parameters: HashMap::new(),
207        }
208    }
209
210    /// Add parameters from a branch
211    pub fn add_branch(
212        &mut self,
213        branch_name: impl Into<String>,
214        parameters: HashMap<String, String>,
215    ) {
216        self.branch_parameters
217            .insert(branch_name.into(), parameters);
218    }
219
220    /// Get parameters from specific branch
221    pub fn get_branch(&self, branch_name: &str) -> Option<&HashMap<String, String>> {
222        self.branch_parameters.get(branch_name)
223    }
224
225    /// Merge all branch parameters (later branches override earlier ones)
226    pub fn merge_all(&self) -> HashMap<String, String> {
227        let mut merged = HashMap::new();
228        for params in self.branch_parameters.values() {
229            merged.extend(params.clone());
230        }
231        merged
232    }
233}
234
235impl Default for ParameterMap {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241/// Pipeline operation types
242#[derive(Debug, Clone)]
243pub enum PipelineOperation {
244    /// Execute algorithm with given name and seed
245    Algorithm { name: String, seed: Option<u64> },
246    /// Apply effect with parameters
247    Effect {
248        name: String,
249        parameters: HashMap<String, String>,
250    },
251    /// Set context parameter
252    SetParameter { key: String, value: String },
253    /// Log message to context
254    Log { message: String },
255}
256
257/// Conditional pipeline with control flow
258#[derive(Debug, Clone)]
259pub struct ConditionalPipeline {
260    /// Pipeline operations to execute
261    operations: Vec<ConditionalOperation>,
262}
263
264/// A conditional operation in the pipeline
265#[derive(Debug, Clone)]
266pub struct ConditionalOperation {
267    /// The operation to perform
268    pub operation: PipelineOperation,
269    /// Optional condition that must be met
270    pub condition: Option<PipelineCondition>,
271    /// Operations to execute if condition is true
272    pub if_true: Vec<ConditionalOperation>,
273    /// Operations to execute if condition is false
274    pub if_false: Vec<ConditionalOperation>,
275}
276
277impl ConditionalPipeline {
278    /// Create new conditional pipeline
279    pub fn new() -> Self {
280        Self {
281            operations: Vec::new(),
282        }
283    }
284
285    /// Add operation to pipeline
286    pub fn add_operation(&mut self, operation: ConditionalOperation) {
287        self.operations.push(operation);
288    }
289
290    /// Execute pipeline on grid with context
291    pub fn execute(
292        &self,
293        grid: &mut Grid<Tile>,
294        context: &mut PipelineContext,
295        rng: &mut Rng,
296    ) -> StageResult {
297        for operation in &self.operations {
298            let result = self.execute_operation(operation, grid, context, rng);
299            if !result.success {
300                return result;
301            }
302
303            // Merge output parameters into context
304            for (key, value) in result.output_parameters {
305                context.set_parameter(key, value);
306            }
307        }
308
309        StageResult::success_with_message("Pipeline executed successfully")
310    }
311
312    /// Execute a single conditional operation
313    fn execute_operation(
314        &self,
315        op: &ConditionalOperation,
316        grid: &mut Grid<Tile>,
317        context: &mut PipelineContext,
318        rng: &mut Rng,
319    ) -> StageResult {
320        // Execute the base operation
321        let mut result = self.execute_base_operation(&op.operation, grid, context, rng);
322
323        // Check condition and execute branches
324        if let Some(condition) = &op.condition {
325            if condition.evaluate(grid, context) {
326                // Execute if_true branch
327                for true_op in &op.if_true {
328                    let branch_result = self.execute_operation(true_op, grid, context, rng);
329                    if !branch_result.success {
330                        return branch_result;
331                    }
332                    // Merge branch parameters
333                    result
334                        .output_parameters
335                        .extend(branch_result.output_parameters);
336                }
337            } else {
338                // Execute if_false branch
339                for false_op in &op.if_false {
340                    let branch_result = self.execute_operation(false_op, grid, context, rng);
341                    if !branch_result.success {
342                        return branch_result;
343                    }
344                    // Merge branch parameters
345                    result
346                        .output_parameters
347                        .extend(branch_result.output_parameters);
348                }
349            }
350        }
351
352        result
353    }
354
355    /// Execute base pipeline operation
356    fn execute_base_operation(
357        &self,
358        operation: &PipelineOperation,
359        grid: &mut Grid<Tile>,
360        context: &mut PipelineContext,
361        _rng: &mut Rng,
362    ) -> StageResult {
363        match operation {
364            PipelineOperation::Algorithm { name, seed } => {
365                if let Some(algo) = crate::algorithms::get(name) {
366                    let use_seed = seed.unwrap_or(12345);
367                    algo.generate(grid, use_seed);
368                    context.log_execution(format!("Algorithm: {} (seed: {})", name, use_seed));
369                    StageResult::success()
370                        .with_parameter("last_algorithm", name.clone())
371                        .with_parameter("last_seed", use_seed.to_string())
372                } else {
373                    StageResult::failure(format!("Unknown algorithm: {}", name))
374                }
375            }
376            PipelineOperation::Effect {
377                name,
378                parameters: _,
379            } => {
380                // Placeholder for effect execution
381                context.log_execution(format!("Effect: {}", name));
382                StageResult::success().with_parameter("last_effect", name.clone())
383            }
384            PipelineOperation::SetParameter { key, value } => {
385                context.set_parameter(key.clone(), value.clone());
386                context.log_execution(format!("Set parameter: {} = {}", key, value));
387                StageResult::success()
388            }
389            PipelineOperation::Log { message } => {
390                context.log_execution(message.clone());
391                StageResult::success()
392            }
393        }
394    }
395}
396
397impl Default for ConditionalPipeline {
398    fn default() -> Self {
399        Self::new()
400    }
401}
402
403impl ConditionalOperation {
404    /// Create simple operation without conditions
405    pub fn simple(operation: PipelineOperation) -> Self {
406        Self {
407            operation,
408            condition: None,
409            if_true: Vec::new(),
410            if_false: Vec::new(),
411        }
412    }
413
414    /// Create conditional operation
415    pub fn conditional(
416        operation: PipelineOperation,
417        condition: PipelineCondition,
418        if_true: Vec<ConditionalOperation>,
419        if_false: Vec<ConditionalOperation>,
420    ) -> Self {
421        Self {
422            operation,
423            condition: Some(condition),
424            if_true,
425            if_false,
426        }
427    }
428}
429
430/// Template for reusable pipeline configurations
431#[derive(Debug, Clone)]
432pub struct PipelineTemplate {
433    /// Template name
434    pub name: String,
435    /// Template description
436    pub description: String,
437    /// Template parameters with default values
438    pub parameters: HashMap<String, String>,
439    /// Pipeline operations (can use parameter placeholders)
440    pub operations: Vec<ConditionalOperation>,
441}
442
443impl PipelineTemplate {
444    /// Create new pipeline template
445    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
446        Self {
447            name: name.into(),
448            description: description.into(),
449            parameters: HashMap::new(),
450            operations: Vec::new(),
451        }
452    }
453
454    /// Add parameter with default value
455    pub fn with_parameter(
456        mut self,
457        key: impl Into<String>,
458        default_value: impl Into<String>,
459    ) -> Self {
460        self.parameters.insert(key.into(), default_value.into());
461        self
462    }
463
464    /// Add operation to template
465    pub fn with_operation(mut self, operation: ConditionalOperation) -> Self {
466        self.operations.push(operation);
467        self
468    }
469
470    /// Instantiate template with custom parameters
471    pub fn instantiate(
472        &self,
473        custom_params: Option<HashMap<String, String>>,
474    ) -> ConditionalPipeline {
475        let mut pipeline = ConditionalPipeline::new();
476
477        // Merge default and custom parameters
478        let mut params = self.parameters.clone();
479        if let Some(custom) = custom_params {
480            params.extend(custom);
481        }
482
483        // Clone operations and substitute parameters
484        for operation in &self.operations {
485            let substituted = self.substitute_parameters(operation, &params);
486            pipeline.add_operation(substituted);
487        }
488
489        pipeline
490    }
491
492    /// Substitute parameter placeholders in operations
493    fn substitute_parameters(
494        &self,
495        operation: &ConditionalOperation,
496        params: &HashMap<String, String>,
497    ) -> ConditionalOperation {
498        let substituted_op = match &operation.operation {
499            PipelineOperation::Algorithm { name, seed } => {
500                let sub_name = self.substitute_string(name, params);
501                PipelineOperation::Algorithm {
502                    name: sub_name,
503                    seed: *seed,
504                }
505            }
506            PipelineOperation::Effect { name, parameters } => {
507                let sub_name = self.substitute_string(name, params);
508                let mut sub_params = HashMap::new();
509                for (k, v) in parameters {
510                    sub_params.insert(k.clone(), self.substitute_string(v, params));
511                }
512                PipelineOperation::Effect {
513                    name: sub_name,
514                    parameters: sub_params,
515                }
516            }
517            PipelineOperation::SetParameter { key, value } => PipelineOperation::SetParameter {
518                key: key.clone(),
519                value: self.substitute_string(value, params),
520            },
521            PipelineOperation::Log { message } => PipelineOperation::Log {
522                message: self.substitute_string(message, params),
523            },
524        };
525
526        // Recursively substitute in branches
527        let sub_if_true: Vec<ConditionalOperation> = operation
528            .if_true
529            .iter()
530            .map(|op| self.substitute_parameters(op, params))
531            .collect();
532        let sub_if_false: Vec<ConditionalOperation> = operation
533            .if_false
534            .iter()
535            .map(|op| self.substitute_parameters(op, params))
536            .collect();
537
538        ConditionalOperation {
539            operation: substituted_op,
540            condition: operation.condition.clone(),
541            if_true: sub_if_true,
542            if_false: sub_if_false,
543        }
544    }
545
546    /// Substitute parameter placeholders in a string
547    fn substitute_string(&self, input: &str, params: &HashMap<String, String>) -> String {
548        let mut result = input.to_string();
549        for (key, value) in params {
550            let placeholder = format!("{{{}}}", key);
551            result = result.replace(&placeholder, value);
552        }
553        result
554    }
555}
556/// Library of built-in pipeline templates
557#[derive(Debug, Clone)]
558pub struct TemplateLibrary {
559    templates: HashMap<String, PipelineTemplate>,
560}
561
562impl TemplateLibrary {
563    /// Create new template library with built-in templates
564    pub fn new() -> Self {
565        let mut library = Self {
566            templates: HashMap::new(),
567        };
568
569        library.add_builtin_templates();
570        library
571    }
572
573    /// Add a template to the library
574    pub fn add_template(&mut self, template: PipelineTemplate) {
575        self.templates.insert(template.name.clone(), template);
576    }
577
578    /// Get template by name
579    pub fn get_template(&self, name: &str) -> Option<&PipelineTemplate> {
580        self.templates.get(name)
581    }
582
583    /// List all template names
584    pub fn template_names(&self) -> Vec<&String> {
585        self.templates.keys().collect()
586    }
587
588    /// Add built-in templates
589    fn add_builtin_templates(&mut self) {
590        // Simple dungeon template
591        let simple_dungeon =
592            PipelineTemplate::new("simple_dungeon", "Basic dungeon with rooms and corridors")
593                .with_parameter("algorithm", "bsp")
594                .with_parameter("seed", "12345")
595                .with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
596                    name: "{algorithm}".to_string(),
597                    seed: Some(12345),
598                }))
599                .with_operation(ConditionalOperation::conditional(
600                    PipelineOperation::Log {
601                        message: "Checking floor density".to_string(),
602                    },
603                    PipelineCondition::Density {
604                        min: Some(0.1),
605                        max: Some(0.8),
606                    },
607                    vec![ConditionalOperation::simple(PipelineOperation::Log {
608                        message: "Density acceptable".to_string(),
609                    })],
610                    vec![ConditionalOperation::simple(PipelineOperation::Log {
611                        message: "Density out of range".to_string(),
612                    })],
613                ));
614
615        self.add_template(simple_dungeon);
616
617        // Cave system template
618        let cave_system =
619            PipelineTemplate::new("cave_system", "Organic cave system with cellular automata")
620                .with_parameter("algorithm", "cellular")
621                .with_parameter("iterations", "5")
622                .with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
623                    name: "{algorithm}".to_string(),
624                    seed: Some(54321),
625                }))
626                .with_operation(ConditionalOperation::simple(
627                    PipelineOperation::SetParameter {
628                        key: "generation_type".to_string(),
629                        value: "cave".to_string(),
630                    },
631                ));
632
633        self.add_template(cave_system);
634
635        // Maze template
636        let maze_template = PipelineTemplate::new("maze", "Perfect maze generation")
637            .with_parameter("algorithm", "maze")
638            .with_parameter("complexity", "medium")
639            .with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
640                name: "{algorithm}".to_string(),
641                seed: Some(98765),
642            }))
643            .with_operation(ConditionalOperation::conditional(
644                PipelineOperation::Log {
645                    message: "Checking connectivity".to_string(),
646                },
647                PipelineCondition::Connected { required: true },
648                vec![ConditionalOperation::simple(PipelineOperation::Log {
649                    message: "Maze is connected".to_string(),
650                })],
651                vec![ConditionalOperation::simple(PipelineOperation::Log {
652                    message: "Warning: Maze may have disconnected areas".to_string(),
653                })],
654            ));
655
656        self.add_template(maze_template);
657    }
658}
659
660impl Default for TemplateLibrary {
661    fn default() -> Self {
662        Self::new()
663    }
664}