Skip to main content

terrain_forge/
pipeline.rs

1//! Pipeline system for conditional generation and ops orchestration.
2//!
3//! Supports algorithms, effects, combine steps, conditionals, and reusable templates.
4//!
5//! ```rust
6//! use terrain_forge::{Grid, pipeline::Pipeline};
7//! use terrain_forge::ops::Params;
8//! use serde_json::json;
9//!
10//! let mut grid = Grid::new(80, 60);
11//! let mut params = Params::new();
12//! params.insert("iterations".to_string(), json!(2));
13//!
14//! let mut pipe = Pipeline::new();
15//! pipe.add_algorithm("cellular", Some(42), None)
16//!     .add_effect("erode", Some(params));
17//!
18//! pipe.execute_seed(&mut grid, 12345).unwrap();
19//! ```
20
21use crate::ops::{self, CombineMode, OpError, Params};
22use crate::{Algorithm, Grid, Rng, Tile};
23use std::collections::HashMap;
24
25/// Unified pipeline steps (name + optional params).
26#[derive(Debug, Clone)]
27pub enum Step {
28    Algorithm {
29        name: String,
30        seed: Option<u64>,
31        params: Option<Params>,
32    },
33    Effect {
34        name: String,
35        params: Option<Params>,
36    },
37    Combine {
38        mode: CombineMode,
39        source: CombineSource,
40    },
41    If {
42        condition: PipelineCondition,
43        then_steps: Vec<Step>,
44        else_steps: Vec<Step>,
45    },
46    StoreGrid {
47        key: String,
48    },
49    SetParameter {
50        key: String,
51        value: String,
52    },
53    Log {
54        message: String,
55    },
56}
57
58/// Source for combine steps.
59#[derive(Debug, Clone)]
60pub enum CombineSource {
61    Grid(Grid<Tile>),
62    Algorithm {
63        name: String,
64        seed: Option<u64>,
65        params: Option<Params>,
66    },
67    Saved(String),
68}
69
70/// Unified pipeline that executes ops::generate/effect/combine.
71#[derive(Debug, Clone, Default)]
72pub struct Pipeline {
73    steps: Vec<Step>,
74}
75
76impl Pipeline {
77    pub fn new() -> Self {
78        Self { steps: Vec::new() }
79    }
80
81    pub fn add_step(&mut self, step: Step) -> &mut Self {
82        self.steps.push(step);
83        self
84    }
85
86    pub fn add_algorithm(
87        &mut self,
88        name: impl Into<String>,
89        seed: Option<u64>,
90        params: Option<Params>,
91    ) -> &mut Self {
92        self.steps.push(Step::Algorithm {
93            name: name.into(),
94            seed,
95            params,
96        });
97        self
98    }
99
100    pub fn add_effect(&mut self, name: impl Into<String>, params: Option<Params>) -> &mut Self {
101        self.steps.push(Step::Effect {
102            name: name.into(),
103            params,
104        });
105        self
106    }
107
108    pub fn add_combine_with_algorithm(
109        &mut self,
110        mode: CombineMode,
111        name: impl Into<String>,
112        seed: Option<u64>,
113        params: Option<Params>,
114    ) -> &mut Self {
115        self.steps.push(Step::Combine {
116            mode,
117            source: CombineSource::Algorithm {
118                name: name.into(),
119                seed,
120                params,
121            },
122        });
123        self
124    }
125
126    pub fn add_combine_with_grid(&mut self, mode: CombineMode, grid: Grid<Tile>) -> &mut Self {
127        self.steps.push(Step::Combine {
128            mode,
129            source: CombineSource::Grid(grid),
130        });
131        self
132    }
133
134    pub fn add_combine_with_saved(
135        &mut self,
136        mode: CombineMode,
137        key: impl Into<String>,
138    ) -> &mut Self {
139        self.steps.push(Step::Combine {
140            mode,
141            source: CombineSource::Saved(key.into()),
142        });
143        self
144    }
145
146    pub fn add_if(
147        &mut self,
148        condition: PipelineCondition,
149        then_steps: Vec<Step>,
150        else_steps: Vec<Step>,
151    ) -> &mut Self {
152        self.steps.push(Step::If {
153            condition,
154            then_steps,
155            else_steps,
156        });
157        self
158    }
159
160    pub fn store_grid(&mut self, key: impl Into<String>) -> &mut Self {
161        self.steps.push(Step::StoreGrid { key: key.into() });
162        self
163    }
164
165    pub fn execute(
166        &self,
167        grid: &mut Grid<Tile>,
168        context: &mut PipelineContext,
169        rng: &mut Rng,
170    ) -> Result<(), OpError> {
171        for step in &self.steps {
172            Self::execute_step(step, grid, context, rng)?;
173        }
174        Ok(())
175    }
176
177    pub fn execute_seed(
178        &self,
179        grid: &mut Grid<Tile>,
180        seed: u64,
181    ) -> Result<PipelineContext, OpError> {
182        let mut context = PipelineContext::new();
183        let mut rng = Rng::new(seed);
184        self.execute(grid, &mut context, &mut rng)?;
185        Ok(context)
186    }
187
188    fn execute_step(
189        step: &Step,
190        grid: &mut Grid<Tile>,
191        context: &mut PipelineContext,
192        rng: &mut Rng,
193    ) -> Result<(), OpError> {
194        match step {
195            Step::Algorithm { name, seed, params } => {
196                let use_seed = seed.unwrap_or_else(|| rng.next_u64());
197                ops::generate(name, grid, Some(use_seed), params.as_ref())?;
198                context.log_execution(format!("Algorithm: {} (seed: {})", name, use_seed));
199                Ok(())
200            }
201            Step::Effect { name, params } => {
202                ops::effect(name, grid, params.as_ref(), None)?;
203                context.log_execution(format!("Effect: {}", name));
204                Ok(())
205            }
206            Step::Combine { mode, source } => {
207                let other = match source {
208                    CombineSource::Grid(other) => other.clone(),
209                    CombineSource::Algorithm { name, seed, params } => {
210                        let mut temp = Grid::new(grid.width(), grid.height());
211                        let use_seed = seed.unwrap_or_else(|| rng.next_u64());
212                        ops::generate(name, &mut temp, Some(use_seed), params.as_ref())?;
213                        temp
214                    }
215                    CombineSource::Saved(key) => context
216                        .get_grid(key)
217                        .ok_or_else(|| OpError::new(format!("Unknown saved grid: {}", key)))?
218                        .clone(),
219                };
220                ops::combine(*mode, grid, &other)?;
221                context.log_execution(format!("Combine: {:?}", mode));
222                Ok(())
223            }
224            Step::If {
225                condition,
226                then_steps,
227                else_steps,
228            } => {
229                let branch = if condition.evaluate(grid, context) {
230                    then_steps
231                } else {
232                    else_steps
233                };
234                for step in branch {
235                    Self::execute_step(step, grid, context, rng)?;
236                }
237                Ok(())
238            }
239            Step::StoreGrid { key } => {
240                context.store_grid(key.clone(), grid.clone());
241                Ok(())
242            }
243            Step::SetParameter { key, value } => {
244                context.set_parameter(key.clone(), value.clone());
245                Ok(())
246            }
247            Step::Log { message } => {
248                context.log_execution(message.clone());
249                Ok(())
250            }
251        }
252    }
253}
254
255impl Algorithm<Tile> for Pipeline {
256    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
257        if let Err(err) = self.execute_seed(grid, seed) {
258            if cfg!(debug_assertions) {
259                eprintln!("Pipeline execution failed: {}", err);
260            }
261        }
262    }
263
264    fn name(&self) -> &'static str {
265        "Pipeline"
266    }
267}
268
269/// Conditions that can be evaluated during pipeline execution
270#[derive(Debug, Clone)]
271pub enum PipelineCondition {
272    /// Check if floor tile count meets threshold
273    FloorCount {
274        min: Option<usize>,
275        max: Option<usize>,
276    },
277    /// Check if region count meets threshold  
278    RegionCount {
279        min: Option<usize>,
280        max: Option<usize>,
281    },
282    /// Check if grid density (floor/total ratio) meets threshold
283    Density { min: Option<f32>, max: Option<f32> },
284    /// Check if connectivity meets requirements
285    Connected { required: bool },
286    /// Custom condition with user-provided function
287    Custom(fn(&Grid<Tile>, &PipelineContext) -> bool),
288}
289
290impl PipelineCondition {
291    /// Evaluate condition against current grid and context
292    pub fn evaluate(&self, grid: &Grid<Tile>, context: &PipelineContext) -> bool {
293        match self {
294            PipelineCondition::FloorCount { min, max } => {
295                let count = grid.count(|t| t.is_floor());
296                if let Some(min_val) = min {
297                    if count < *min_val {
298                        return false;
299                    }
300                }
301                if let Some(max_val) = max {
302                    if count > *max_val {
303                        return false;
304                    }
305                }
306                true
307            }
308            PipelineCondition::RegionCount { min, max } => {
309                let count = context
310                    .get_parameter("region_count")
311                    .and_then(|v| v.parse::<usize>().ok())
312                    .unwrap_or(0);
313                if let Some(min_val) = min {
314                    if count < *min_val {
315                        return false;
316                    }
317                }
318                if let Some(max_val) = max {
319                    if count > *max_val {
320                        return false;
321                    }
322                }
323                true
324            }
325            PipelineCondition::Density { min, max } => {
326                let total = grid.width() * grid.height();
327                let floors = grid.count(|t| t.is_floor());
328                let density = floors as f32 / total as f32;
329                if let Some(min_val) = min {
330                    if density < *min_val {
331                        return false;
332                    }
333                }
334                if let Some(max_val) = max {
335                    if density > *max_val {
336                        return false;
337                    }
338                }
339                true
340            }
341            PipelineCondition::Connected { required } => {
342                // Simple connectivity check - assume connected if we have floors
343                let has_floors = grid.count(|t| t.is_floor()) > 0;
344                has_floors == *required
345            }
346            PipelineCondition::Custom(func) => func(grid, context),
347        }
348    }
349}
350
351/// Context for passing data between pipeline stages
352#[derive(Debug, Clone)]
353pub struct PipelineContext {
354    /// Key-value parameters passed between stages
355    parameters: HashMap<String, String>,
356    /// Stage execution history
357    execution_log: Vec<String>,
358    /// Current iteration count for loops
359    iteration_count: usize,
360    /// Named grids for combine steps
361    grids: HashMap<String, Grid<Tile>>,
362}
363
364impl PipelineContext {
365    /// Create new empty context
366    pub fn new() -> Self {
367        Self {
368            parameters: HashMap::new(),
369            execution_log: Vec::new(),
370            iteration_count: 0,
371            grids: HashMap::new(),
372        }
373    }
374
375    /// Set a parameter value
376    pub fn set_parameter(&mut self, key: impl Into<String>, value: impl Into<String>) {
377        self.parameters.insert(key.into(), value.into());
378    }
379
380    /// Get a parameter value
381    pub fn get_parameter(&self, key: &str) -> Option<&String> {
382        self.parameters.get(key)
383    }
384
385    /// Log stage execution
386    pub fn log_execution(&mut self, stage: impl Into<String>) {
387        self.execution_log.push(stage.into());
388    }
389
390    /// Get execution history
391    pub fn execution_history(&self) -> &[String] {
392        &self.execution_log
393    }
394
395    /// Increment iteration counter
396    pub fn increment_iteration(&mut self) {
397        self.iteration_count += 1;
398    }
399
400    /// Get current iteration count
401    pub fn iteration_count(&self) -> usize {
402        self.iteration_count
403    }
404
405    /// Store a grid snapshot for later use.
406    pub fn store_grid(&mut self, key: impl Into<String>, grid: Grid<Tile>) {
407        self.grids.insert(key.into(), grid);
408    }
409
410    /// Get a stored grid snapshot.
411    pub fn get_grid(&self, key: &str) -> Option<&Grid<Tile>> {
412        self.grids.get(key)
413    }
414}
415
416impl Default for PipelineContext {
417    fn default() -> Self {
418        Self::new()
419    }
420}
421
422/// Result from a pipeline stage execution
423#[derive(Debug, Clone)]
424pub struct StageResult {
425    /// Whether the stage succeeded
426    pub success: bool,
427    /// Optional message about execution
428    pub message: Option<String>,
429    /// Parameters to pass to next stage
430    pub output_parameters: HashMap<String, String>,
431}
432
433impl StageResult {
434    /// Create successful result
435    pub fn success() -> Self {
436        Self {
437            success: true,
438            message: None,
439            output_parameters: HashMap::new(),
440        }
441    }
442
443    /// Create successful result with message
444    pub fn success_with_message(message: impl Into<String>) -> Self {
445        Self {
446            success: true,
447            message: Some(message.into()),
448            output_parameters: HashMap::new(),
449        }
450    }
451
452    /// Create failed result
453    pub fn failure(message: impl Into<String>) -> Self {
454        Self {
455            success: false,
456            message: Some(message.into()),
457            output_parameters: HashMap::new(),
458        }
459    }
460
461    /// Add output parameter
462    pub fn with_parameter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
463        self.output_parameters.insert(key.into(), value.into());
464        self
465    }
466}
467
468/// Map for aggregating parameters from multiple pipeline branches
469#[derive(Debug, Clone)]
470pub struct ParameterMap {
471    /// Parameters from different branches
472    branch_parameters: HashMap<String, HashMap<String, String>>,
473}
474
475impl ParameterMap {
476    /// Create new parameter map
477    pub fn new() -> Self {
478        Self {
479            branch_parameters: HashMap::new(),
480        }
481    }
482
483    /// Add parameters from a branch
484    pub fn add_branch(
485        &mut self,
486        branch_name: impl Into<String>,
487        parameters: HashMap<String, String>,
488    ) {
489        self.branch_parameters
490            .insert(branch_name.into(), parameters);
491    }
492
493    /// Get parameters from specific branch
494    pub fn get_branch(&self, branch_name: &str) -> Option<&HashMap<String, String>> {
495        self.branch_parameters.get(branch_name)
496    }
497
498    /// Merge all branch parameters (later branches override earlier ones)
499    pub fn merge_all(&self) -> HashMap<String, String> {
500        let mut merged = HashMap::new();
501        for params in self.branch_parameters.values() {
502            merged.extend(params.clone());
503        }
504        merged
505    }
506}
507
508impl Default for ParameterMap {
509    fn default() -> Self {
510        Self::new()
511    }
512}
513
514/// Pipeline operation types
515#[derive(Debug, Clone)]
516pub enum PipelineOperation {
517    /// Execute algorithm with given name and seed
518    Algorithm { name: String, seed: Option<u64> },
519    /// Apply effect with parameters
520    Effect {
521        name: String,
522        parameters: HashMap<String, String>,
523    },
524    /// Set context parameter
525    SetParameter { key: String, value: String },
526    /// Log message to context
527    Log { message: String },
528}
529
530/// Conditional pipeline with control flow
531#[derive(Debug, Clone)]
532pub struct ConditionalPipeline {
533    /// Pipeline operations to execute
534    operations: Vec<ConditionalOperation>,
535}
536
537/// A conditional operation in the pipeline
538#[derive(Debug, Clone)]
539pub struct ConditionalOperation {
540    /// The operation to perform
541    pub operation: PipelineOperation,
542    /// Optional condition that must be met
543    pub condition: Option<PipelineCondition>,
544    /// Operations to execute if condition is true
545    pub if_true: Vec<ConditionalOperation>,
546    /// Operations to execute if condition is false
547    pub if_false: Vec<ConditionalOperation>,
548}
549
550impl ConditionalPipeline {
551    /// Create new conditional pipeline
552    pub fn new() -> Self {
553        Self {
554            operations: Vec::new(),
555        }
556    }
557
558    /// Add operation to pipeline
559    pub fn add_operation(&mut self, operation: ConditionalOperation) {
560        self.operations.push(operation);
561    }
562
563    /// Execute pipeline on grid with context
564    pub fn execute(
565        &self,
566        grid: &mut Grid<Tile>,
567        context: &mut PipelineContext,
568        rng: &mut Rng,
569    ) -> StageResult {
570        for operation in &self.operations {
571            let result = self.execute_operation(operation, grid, context, rng);
572            if !result.success {
573                return result;
574            }
575
576            // Merge output parameters into context
577            for (key, value) in result.output_parameters {
578                context.set_parameter(key, value);
579            }
580        }
581
582        StageResult::success_with_message("Pipeline executed successfully")
583    }
584
585    /// Execute a single conditional operation
586    fn execute_operation(
587        &self,
588        op: &ConditionalOperation,
589        grid: &mut Grid<Tile>,
590        context: &mut PipelineContext,
591        rng: &mut Rng,
592    ) -> StageResult {
593        // Execute the base operation
594        let mut result = self.execute_base_operation(&op.operation, grid, context, rng);
595
596        // Check condition and execute branches
597        if let Some(condition) = &op.condition {
598            if condition.evaluate(grid, context) {
599                // Execute if_true branch
600                for true_op in &op.if_true {
601                    let branch_result = self.execute_operation(true_op, grid, context, rng);
602                    if !branch_result.success {
603                        return branch_result;
604                    }
605                    // Merge branch parameters
606                    result
607                        .output_parameters
608                        .extend(branch_result.output_parameters);
609                }
610            } else {
611                // Execute if_false branch
612                for false_op in &op.if_false {
613                    let branch_result = self.execute_operation(false_op, grid, context, rng);
614                    if !branch_result.success {
615                        return branch_result;
616                    }
617                    // Merge branch parameters
618                    result
619                        .output_parameters
620                        .extend(branch_result.output_parameters);
621                }
622            }
623        }
624
625        result
626    }
627
628    /// Execute base pipeline operation
629    fn execute_base_operation(
630        &self,
631        operation: &PipelineOperation,
632        grid: &mut Grid<Tile>,
633        context: &mut PipelineContext,
634        _rng: &mut Rng,
635    ) -> StageResult {
636        match operation {
637            PipelineOperation::Algorithm { name, seed } => {
638                let use_seed = seed.unwrap_or(12345);
639                match ops::generate(name, grid, Some(use_seed), None) {
640                    Ok(()) => {
641                        context.log_execution(format!("Algorithm: {} (seed: {})", name, use_seed));
642                        StageResult::success()
643                            .with_parameter("last_algorithm", name.clone())
644                            .with_parameter("last_seed", use_seed.to_string())
645                    }
646                    Err(err) => StageResult::failure(err.to_string()),
647                }
648            }
649            PipelineOperation::Effect { name, parameters } => {
650                let params = params_from_strings(parameters);
651                match ops::effect(name, grid, Some(&params), None) {
652                    Ok(()) => {
653                        context.log_execution(format!("Effect: {}", name));
654                        StageResult::success().with_parameter("last_effect", name.clone())
655                    }
656                    Err(err) => StageResult::failure(err.to_string()),
657                }
658            }
659            PipelineOperation::SetParameter { key, value } => {
660                context.set_parameter(key.clone(), value.clone());
661                context.log_execution(format!("Set parameter: {} = {}", key, value));
662                StageResult::success()
663            }
664            PipelineOperation::Log { message } => {
665                context.log_execution(message.clone());
666                StageResult::success()
667            }
668        }
669    }
670}
671
672impl Default for ConditionalPipeline {
673    fn default() -> Self {
674        Self::new()
675    }
676}
677
678impl ConditionalOperation {
679    /// Create simple operation without conditions
680    pub fn simple(operation: PipelineOperation) -> Self {
681        Self {
682            operation,
683            condition: None,
684            if_true: Vec::new(),
685            if_false: Vec::new(),
686        }
687    }
688
689    /// Create conditional operation
690    pub fn conditional(
691        operation: PipelineOperation,
692        condition: PipelineCondition,
693        if_true: Vec<ConditionalOperation>,
694        if_false: Vec<ConditionalOperation>,
695    ) -> Self {
696        Self {
697            operation,
698            condition: Some(condition),
699            if_true,
700            if_false,
701        }
702    }
703}
704
705fn params_from_strings(parameters: &HashMap<String, String>) -> Params {
706    parameters
707        .iter()
708        .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
709        .collect()
710}
711
712/// Template for reusable pipeline configurations
713#[derive(Debug, Clone)]
714pub struct PipelineTemplate {
715    /// Template name
716    pub name: String,
717    /// Template description
718    pub description: String,
719    /// Template parameters with default values
720    pub parameters: HashMap<String, String>,
721    /// Pipeline operations (can use parameter placeholders)
722    pub operations: Vec<ConditionalOperation>,
723}
724
725impl PipelineTemplate {
726    /// Create new pipeline template
727    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
728        Self {
729            name: name.into(),
730            description: description.into(),
731            parameters: HashMap::new(),
732            operations: Vec::new(),
733        }
734    }
735
736    /// Add parameter with default value
737    pub fn with_parameter(
738        mut self,
739        key: impl Into<String>,
740        default_value: impl Into<String>,
741    ) -> Self {
742        self.parameters.insert(key.into(), default_value.into());
743        self
744    }
745
746    /// Add operation to template
747    pub fn with_operation(mut self, operation: ConditionalOperation) -> Self {
748        self.operations.push(operation);
749        self
750    }
751
752    /// Instantiate template with custom parameters
753    pub fn instantiate(
754        &self,
755        custom_params: Option<HashMap<String, String>>,
756    ) -> ConditionalPipeline {
757        let mut pipeline = ConditionalPipeline::new();
758
759        // Merge default and custom parameters
760        let mut params = self.parameters.clone();
761        if let Some(custom) = custom_params {
762            params.extend(custom);
763        }
764
765        // Clone operations and substitute parameters
766        for operation in &self.operations {
767            let substituted = self.substitute_parameters(operation, &params);
768            pipeline.add_operation(substituted);
769        }
770
771        pipeline
772    }
773
774    /// Substitute parameter placeholders in operations
775    fn substitute_parameters(
776        &self,
777        operation: &ConditionalOperation,
778        params: &HashMap<String, String>,
779    ) -> ConditionalOperation {
780        let substituted_op = match &operation.operation {
781            PipelineOperation::Algorithm { name, seed } => {
782                let sub_name = self.substitute_string(name, params);
783                PipelineOperation::Algorithm {
784                    name: sub_name,
785                    seed: *seed,
786                }
787            }
788            PipelineOperation::Effect { name, parameters } => {
789                let sub_name = self.substitute_string(name, params);
790                let mut sub_params = HashMap::new();
791                for (k, v) in parameters {
792                    sub_params.insert(k.clone(), self.substitute_string(v, params));
793                }
794                PipelineOperation::Effect {
795                    name: sub_name,
796                    parameters: sub_params,
797                }
798            }
799            PipelineOperation::SetParameter { key, value } => PipelineOperation::SetParameter {
800                key: key.clone(),
801                value: self.substitute_string(value, params),
802            },
803            PipelineOperation::Log { message } => PipelineOperation::Log {
804                message: self.substitute_string(message, params),
805            },
806        };
807
808        // Recursively substitute in branches
809        let sub_if_true: Vec<ConditionalOperation> = operation
810            .if_true
811            .iter()
812            .map(|op| self.substitute_parameters(op, params))
813            .collect();
814        let sub_if_false: Vec<ConditionalOperation> = operation
815            .if_false
816            .iter()
817            .map(|op| self.substitute_parameters(op, params))
818            .collect();
819
820        ConditionalOperation {
821            operation: substituted_op,
822            condition: operation.condition.clone(),
823            if_true: sub_if_true,
824            if_false: sub_if_false,
825        }
826    }
827
828    /// Substitute parameter placeholders in a string
829    fn substitute_string(&self, input: &str, params: &HashMap<String, String>) -> String {
830        let mut result = input.to_string();
831        for (key, value) in params {
832            let placeholder = format!("{{{}}}", key);
833            result = result.replace(&placeholder, value);
834        }
835        result
836    }
837}
838/// Library of built-in pipeline templates
839#[derive(Debug, Clone)]
840pub struct TemplateLibrary {
841    templates: HashMap<String, PipelineTemplate>,
842}
843
844impl TemplateLibrary {
845    /// Create new template library with built-in templates
846    pub fn new() -> Self {
847        let mut library = Self {
848            templates: HashMap::new(),
849        };
850
851        library.add_builtin_templates();
852        library
853    }
854
855    /// Add a template to the library
856    pub fn add_template(&mut self, template: PipelineTemplate) {
857        self.templates.insert(template.name.clone(), template);
858    }
859
860    /// Get template by name
861    pub fn get_template(&self, name: &str) -> Option<&PipelineTemplate> {
862        self.templates.get(name)
863    }
864
865    /// List all template names
866    pub fn template_names(&self) -> Vec<&String> {
867        self.templates.keys().collect()
868    }
869
870    /// Add built-in templates
871    fn add_builtin_templates(&mut self) {
872        // Simple dungeon template
873        let simple_dungeon =
874            PipelineTemplate::new("simple_dungeon", "Basic dungeon with rooms and corridors")
875                .with_parameter("algorithm", "bsp")
876                .with_parameter("seed", "12345")
877                .with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
878                    name: "{algorithm}".to_string(),
879                    seed: Some(12345),
880                }))
881                .with_operation(ConditionalOperation::conditional(
882                    PipelineOperation::Log {
883                        message: "Checking floor density".to_string(),
884                    },
885                    PipelineCondition::Density {
886                        min: Some(0.1),
887                        max: Some(0.8),
888                    },
889                    vec![ConditionalOperation::simple(PipelineOperation::Log {
890                        message: "Density acceptable".to_string(),
891                    })],
892                    vec![ConditionalOperation::simple(PipelineOperation::Log {
893                        message: "Density out of range".to_string(),
894                    })],
895                ));
896
897        self.add_template(simple_dungeon);
898
899        // Cave system template
900        let cave_system =
901            PipelineTemplate::new("cave_system", "Organic cave system with cellular automata")
902                .with_parameter("algorithm", "cellular")
903                .with_parameter("iterations", "5")
904                .with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
905                    name: "{algorithm}".to_string(),
906                    seed: Some(54321),
907                }))
908                .with_operation(ConditionalOperation::simple(
909                    PipelineOperation::SetParameter {
910                        key: "generation_type".to_string(),
911                        value: "cave".to_string(),
912                    },
913                ));
914
915        self.add_template(cave_system);
916
917        // Maze template
918        let maze_template = PipelineTemplate::new("maze", "Perfect maze generation")
919            .with_parameter("algorithm", "maze")
920            .with_parameter("complexity", "medium")
921            .with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
922                name: "{algorithm}".to_string(),
923                seed: Some(98765),
924            }))
925            .with_operation(ConditionalOperation::conditional(
926                PipelineOperation::Log {
927                    message: "Checking connectivity".to_string(),
928                },
929                PipelineCondition::Connected { required: true },
930                vec![ConditionalOperation::simple(PipelineOperation::Log {
931                    message: "Maze is connected".to_string(),
932                })],
933                vec![ConditionalOperation::simple(PipelineOperation::Log {
934                    message: "Warning: Maze may have disconnected areas".to_string(),
935                })],
936            ));
937
938        self.add_template(maze_template);
939    }
940}
941
942impl Default for TemplateLibrary {
943    fn default() -> Self {
944        Self::new()
945    }
946}