1use crate::{Grid, Rng, Tile};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub enum PipelineCondition {
12 FloorCount {
14 min: Option<usize>,
15 max: Option<usize>,
16 },
17 RegionCount {
19 min: Option<usize>,
20 max: Option<usize>,
21 },
22 Density { min: Option<f32>, max: Option<f32> },
24 Connected { required: bool },
26 Custom(fn(&Grid<Tile>, &PipelineContext) -> bool),
28}
29
30impl PipelineCondition {
31 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 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#[derive(Debug, Clone)]
93pub struct PipelineContext {
94 parameters: HashMap<String, String>,
96 execution_log: Vec<String>,
98 iteration_count: usize,
100}
101
102impl PipelineContext {
103 pub fn new() -> Self {
105 Self {
106 parameters: HashMap::new(),
107 execution_log: Vec::new(),
108 iteration_count: 0,
109 }
110 }
111
112 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 pub fn get_parameter(&self, key: &str) -> Option<&String> {
119 self.parameters.get(key)
120 }
121
122 pub fn log_execution(&mut self, stage: impl Into<String>) {
124 self.execution_log.push(stage.into());
125 }
126
127 pub fn execution_history(&self) -> &[String] {
129 &self.execution_log
130 }
131
132 pub fn increment_iteration(&mut self) {
134 self.iteration_count += 1;
135 }
136
137 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#[derive(Debug, Clone)]
151pub struct StageResult {
152 pub success: bool,
154 pub message: Option<String>,
156 pub output_parameters: HashMap<String, String>,
158}
159
160impl StageResult {
161 pub fn success() -> Self {
163 Self {
164 success: true,
165 message: None,
166 output_parameters: HashMap::new(),
167 }
168 }
169
170 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 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 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#[derive(Debug, Clone)]
197pub struct ParameterMap {
198 branch_parameters: HashMap<String, HashMap<String, String>>,
200}
201
202impl ParameterMap {
203 pub fn new() -> Self {
205 Self {
206 branch_parameters: HashMap::new(),
207 }
208 }
209
210 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 pub fn get_branch(&self, branch_name: &str) -> Option<&HashMap<String, String>> {
222 self.branch_parameters.get(branch_name)
223 }
224
225 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#[derive(Debug, Clone)]
243pub enum PipelineOperation {
244 Algorithm { name: String, seed: Option<u64> },
246 Effect {
248 name: String,
249 parameters: HashMap<String, String>,
250 },
251 SetParameter { key: String, value: String },
253 Log { message: String },
255}
256
257#[derive(Debug, Clone)]
259pub struct ConditionalPipeline {
260 operations: Vec<ConditionalOperation>,
262}
263
264#[derive(Debug, Clone)]
266pub struct ConditionalOperation {
267 pub operation: PipelineOperation,
269 pub condition: Option<PipelineCondition>,
271 pub if_true: Vec<ConditionalOperation>,
273 pub if_false: Vec<ConditionalOperation>,
275}
276
277impl ConditionalPipeline {
278 pub fn new() -> Self {
280 Self {
281 operations: Vec::new(),
282 }
283 }
284
285 pub fn add_operation(&mut self, operation: ConditionalOperation) {
287 self.operations.push(operation);
288 }
289
290 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 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 fn execute_operation(
314 &self,
315 op: &ConditionalOperation,
316 grid: &mut Grid<Tile>,
317 context: &mut PipelineContext,
318 rng: &mut Rng,
319 ) -> StageResult {
320 let mut result = self.execute_base_operation(&op.operation, grid, context, rng);
322
323 if let Some(condition) = &op.condition {
325 if condition.evaluate(grid, context) {
326 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 result
334 .output_parameters
335 .extend(branch_result.output_parameters);
336 }
337 } else {
338 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 result
346 .output_parameters
347 .extend(branch_result.output_parameters);
348 }
349 }
350 }
351
352 result
353 }
354
355 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 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 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 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#[derive(Debug, Clone)]
432pub struct PipelineTemplate {
433 pub name: String,
435 pub description: String,
437 pub parameters: HashMap<String, String>,
439 pub operations: Vec<ConditionalOperation>,
441}
442
443impl PipelineTemplate {
444 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 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 pub fn with_operation(mut self, operation: ConditionalOperation) -> Self {
466 self.operations.push(operation);
467 self
468 }
469
470 pub fn instantiate(
472 &self,
473 custom_params: Option<HashMap<String, String>>,
474 ) -> ConditionalPipeline {
475 let mut pipeline = ConditionalPipeline::new();
476
477 let mut params = self.parameters.clone();
479 if let Some(custom) = custom_params {
480 params.extend(custom);
481 }
482
483 for operation in &self.operations {
485 let substituted = self.substitute_parameters(operation, ¶ms);
486 pipeline.add_operation(substituted);
487 }
488
489 pipeline
490 }
491
492 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 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 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#[derive(Debug, Clone)]
558pub struct TemplateLibrary {
559 templates: HashMap<String, PipelineTemplate>,
560}
561
562impl TemplateLibrary {
563 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 pub fn add_template(&mut self, template: PipelineTemplate) {
575 self.templates.insert(template.name.clone(), template);
576 }
577
578 pub fn get_template(&self, name: &str) -> Option<&PipelineTemplate> {
580 self.templates.get(name)
581 }
582
583 pub fn template_names(&self) -> Vec<&String> {
585 self.templates.keys().collect()
586 }
587
588 fn add_builtin_templates(&mut self) {
590 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 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 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}