1use crate::ops::{self, CombineMode, OpError, Params};
22use crate::{Algorithm, Grid, Rng, Tile};
23use std::collections::HashMap;
24
25#[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#[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#[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#[derive(Debug, Clone)]
271pub enum PipelineCondition {
272 FloorCount {
274 min: Option<usize>,
275 max: Option<usize>,
276 },
277 RegionCount {
279 min: Option<usize>,
280 max: Option<usize>,
281 },
282 Density { min: Option<f32>, max: Option<f32> },
284 Connected { required: bool },
286 Custom(fn(&Grid<Tile>, &PipelineContext) -> bool),
288}
289
290impl PipelineCondition {
291 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 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#[derive(Debug, Clone)]
353pub struct PipelineContext {
354 parameters: HashMap<String, String>,
356 execution_log: Vec<String>,
358 iteration_count: usize,
360 grids: HashMap<String, Grid<Tile>>,
362}
363
364impl PipelineContext {
365 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 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 pub fn get_parameter(&self, key: &str) -> Option<&String> {
382 self.parameters.get(key)
383 }
384
385 pub fn log_execution(&mut self, stage: impl Into<String>) {
387 self.execution_log.push(stage.into());
388 }
389
390 pub fn execution_history(&self) -> &[String] {
392 &self.execution_log
393 }
394
395 pub fn increment_iteration(&mut self) {
397 self.iteration_count += 1;
398 }
399
400 pub fn iteration_count(&self) -> usize {
402 self.iteration_count
403 }
404
405 pub fn store_grid(&mut self, key: impl Into<String>, grid: Grid<Tile>) {
407 self.grids.insert(key.into(), grid);
408 }
409
410 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#[derive(Debug, Clone)]
424pub struct StageResult {
425 pub success: bool,
427 pub message: Option<String>,
429 pub output_parameters: HashMap<String, String>,
431}
432
433impl StageResult {
434 pub fn success() -> Self {
436 Self {
437 success: true,
438 message: None,
439 output_parameters: HashMap::new(),
440 }
441 }
442
443 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 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 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#[derive(Debug, Clone)]
470pub struct ParameterMap {
471 branch_parameters: HashMap<String, HashMap<String, String>>,
473}
474
475impl ParameterMap {
476 pub fn new() -> Self {
478 Self {
479 branch_parameters: HashMap::new(),
480 }
481 }
482
483 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 pub fn get_branch(&self, branch_name: &str) -> Option<&HashMap<String, String>> {
495 self.branch_parameters.get(branch_name)
496 }
497
498 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#[derive(Debug, Clone)]
516pub enum PipelineOperation {
517 Algorithm { name: String, seed: Option<u64> },
519 Effect {
521 name: String,
522 parameters: HashMap<String, String>,
523 },
524 SetParameter { key: String, value: String },
526 Log { message: String },
528}
529
530#[derive(Debug, Clone)]
532pub struct ConditionalPipeline {
533 operations: Vec<ConditionalOperation>,
535}
536
537#[derive(Debug, Clone)]
539pub struct ConditionalOperation {
540 pub operation: PipelineOperation,
542 pub condition: Option<PipelineCondition>,
544 pub if_true: Vec<ConditionalOperation>,
546 pub if_false: Vec<ConditionalOperation>,
548}
549
550impl ConditionalPipeline {
551 pub fn new() -> Self {
553 Self {
554 operations: Vec::new(),
555 }
556 }
557
558 pub fn add_operation(&mut self, operation: ConditionalOperation) {
560 self.operations.push(operation);
561 }
562
563 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 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 fn execute_operation(
587 &self,
588 op: &ConditionalOperation,
589 grid: &mut Grid<Tile>,
590 context: &mut PipelineContext,
591 rng: &mut Rng,
592 ) -> StageResult {
593 let mut result = self.execute_base_operation(&op.operation, grid, context, rng);
595
596 if let Some(condition) = &op.condition {
598 if condition.evaluate(grid, context) {
599 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 result
607 .output_parameters
608 .extend(branch_result.output_parameters);
609 }
610 } else {
611 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 result
619 .output_parameters
620 .extend(branch_result.output_parameters);
621 }
622 }
623 }
624
625 result
626 }
627
628 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(¶ms), 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 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 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#[derive(Debug, Clone)]
714pub struct PipelineTemplate {
715 pub name: String,
717 pub description: String,
719 pub parameters: HashMap<String, String>,
721 pub operations: Vec<ConditionalOperation>,
723}
724
725impl PipelineTemplate {
726 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 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 pub fn with_operation(mut self, operation: ConditionalOperation) -> Self {
748 self.operations.push(operation);
749 self
750 }
751
752 pub fn instantiate(
754 &self,
755 custom_params: Option<HashMap<String, String>>,
756 ) -> ConditionalPipeline {
757 let mut pipeline = ConditionalPipeline::new();
758
759 let mut params = self.parameters.clone();
761 if let Some(custom) = custom_params {
762 params.extend(custom);
763 }
764
765 for operation in &self.operations {
767 let substituted = self.substitute_parameters(operation, ¶ms);
768 pipeline.add_operation(substituted);
769 }
770
771 pipeline
772 }
773
774 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 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 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#[derive(Debug, Clone)]
840pub struct TemplateLibrary {
841 templates: HashMap<String, PipelineTemplate>,
842}
843
844impl TemplateLibrary {
845 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 pub fn add_template(&mut self, template: PipelineTemplate) {
857 self.templates.insert(template.name.clone(), template);
858 }
859
860 pub fn get_template(&self, name: &str) -> Option<&PipelineTemplate> {
862 self.templates.get(name)
863 }
864
865 pub fn template_names(&self) -> Vec<&String> {
867 self.templates.keys().collect()
868 }
869
870 fn add_builtin_templates(&mut self) {
872 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 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 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}