use crate::ops::{self, CombineMode, OpError, Params};
use crate::{Algorithm, Grid, Rng, Tile};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum Step {
Algorithm {
name: String,
seed: Option<u64>,
params: Option<Params>,
},
Effect {
name: String,
params: Option<Params>,
},
Combine {
mode: CombineMode,
source: CombineSource,
},
If {
condition: PipelineCondition,
then_steps: Vec<Step>,
else_steps: Vec<Step>,
},
StoreGrid {
key: String,
},
SetParameter {
key: String,
value: String,
},
Log {
message: String,
},
}
#[derive(Debug, Clone)]
pub enum CombineSource {
Grid(Grid<Tile>),
Algorithm {
name: String,
seed: Option<u64>,
params: Option<Params>,
},
Saved(String),
}
#[derive(Debug, Clone, Default)]
pub struct Pipeline {
steps: Vec<Step>,
}
impl Pipeline {
pub fn new() -> Self {
Self { steps: Vec::new() }
}
pub fn add_step(&mut self, step: Step) -> &mut Self {
self.steps.push(step);
self
}
pub fn add_algorithm(
&mut self,
name: impl Into<String>,
seed: Option<u64>,
params: Option<Params>,
) -> &mut Self {
self.steps.push(Step::Algorithm {
name: name.into(),
seed,
params,
});
self
}
pub fn add_effect(&mut self, name: impl Into<String>, params: Option<Params>) -> &mut Self {
self.steps.push(Step::Effect {
name: name.into(),
params,
});
self
}
pub fn add_combine_with_algorithm(
&mut self,
mode: CombineMode,
name: impl Into<String>,
seed: Option<u64>,
params: Option<Params>,
) -> &mut Self {
self.steps.push(Step::Combine {
mode,
source: CombineSource::Algorithm {
name: name.into(),
seed,
params,
},
});
self
}
pub fn add_combine_with_grid(&mut self, mode: CombineMode, grid: Grid<Tile>) -> &mut Self {
self.steps.push(Step::Combine {
mode,
source: CombineSource::Grid(grid),
});
self
}
pub fn add_combine_with_saved(
&mut self,
mode: CombineMode,
key: impl Into<String>,
) -> &mut Self {
self.steps.push(Step::Combine {
mode,
source: CombineSource::Saved(key.into()),
});
self
}
pub fn add_if(
&mut self,
condition: PipelineCondition,
then_steps: Vec<Step>,
else_steps: Vec<Step>,
) -> &mut Self {
self.steps.push(Step::If {
condition,
then_steps,
else_steps,
});
self
}
pub fn store_grid(&mut self, key: impl Into<String>) -> &mut Self {
self.steps.push(Step::StoreGrid { key: key.into() });
self
}
pub fn execute(
&self,
grid: &mut Grid<Tile>,
context: &mut PipelineContext,
rng: &mut Rng,
) -> Result<(), OpError> {
for step in &self.steps {
Self::execute_step(step, grid, context, rng)?;
}
Ok(())
}
pub fn execute_seed(
&self,
grid: &mut Grid<Tile>,
seed: u64,
) -> Result<PipelineContext, OpError> {
let mut context = PipelineContext::new();
let mut rng = Rng::new(seed);
self.execute(grid, &mut context, &mut rng)?;
Ok(context)
}
fn execute_step(
step: &Step,
grid: &mut Grid<Tile>,
context: &mut PipelineContext,
rng: &mut Rng,
) -> Result<(), OpError> {
match step {
Step::Algorithm { name, seed, params } => {
let use_seed = seed.unwrap_or_else(|| rng.next_u64());
ops::generate(name, grid, Some(use_seed), params.as_ref())?;
context.log_execution(format!("Algorithm: {} (seed: {})", name, use_seed));
Ok(())
}
Step::Effect { name, params } => {
ops::effect(name, grid, params.as_ref(), None)?;
context.log_execution(format!("Effect: {}", name));
Ok(())
}
Step::Combine { mode, source } => {
let other = match source {
CombineSource::Grid(other) => other.clone(),
CombineSource::Algorithm { name, seed, params } => {
let mut temp = Grid::new(grid.width(), grid.height());
let use_seed = seed.unwrap_or_else(|| rng.next_u64());
ops::generate(name, &mut temp, Some(use_seed), params.as_ref())?;
temp
}
CombineSource::Saved(key) => context
.get_grid(key)
.ok_or_else(|| OpError::new(format!("Unknown saved grid: {}", key)))?
.clone(),
};
ops::combine(*mode, grid, &other)?;
context.log_execution(format!("Combine: {:?}", mode));
Ok(())
}
Step::If {
condition,
then_steps,
else_steps,
} => {
let branch = if condition.evaluate(grid, context) {
then_steps
} else {
else_steps
};
for step in branch {
Self::execute_step(step, grid, context, rng)?;
}
Ok(())
}
Step::StoreGrid { key } => {
context.store_grid(key.clone(), grid.clone());
Ok(())
}
Step::SetParameter { key, value } => {
context.set_parameter(key.clone(), value.clone());
Ok(())
}
Step::Log { message } => {
context.log_execution(message.clone());
Ok(())
}
}
}
}
impl Algorithm<Tile> for Pipeline {
fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
if let Err(err) = self.execute_seed(grid, seed) {
if cfg!(debug_assertions) {
eprintln!("Pipeline execution failed: {}", err);
}
}
}
fn name(&self) -> &'static str {
"Pipeline"
}
}
#[derive(Debug, Clone)]
pub enum PipelineCondition {
FloorCount {
min: Option<usize>,
max: Option<usize>,
},
RegionCount {
min: Option<usize>,
max: Option<usize>,
},
Density { min: Option<f32>, max: Option<f32> },
Connected { required: bool },
Custom(fn(&Grid<Tile>, &PipelineContext) -> bool),
}
impl PipelineCondition {
pub fn evaluate(&self, grid: &Grid<Tile>, context: &PipelineContext) -> bool {
match self {
PipelineCondition::FloorCount { min, max } => {
let count = grid.count(|t| t.is_floor());
if let Some(min_val) = min {
if count < *min_val {
return false;
}
}
if let Some(max_val) = max {
if count > *max_val {
return false;
}
}
true
}
PipelineCondition::RegionCount { min, max } => {
let count = context
.get_parameter("region_count")
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
if let Some(min_val) = min {
if count < *min_val {
return false;
}
}
if let Some(max_val) = max {
if count > *max_val {
return false;
}
}
true
}
PipelineCondition::Density { min, max } => {
let total = grid.width() * grid.height();
let floors = grid.count(|t| t.is_floor());
let density = floors as f32 / total as f32;
if let Some(min_val) = min {
if density < *min_val {
return false;
}
}
if let Some(max_val) = max {
if density > *max_val {
return false;
}
}
true
}
PipelineCondition::Connected { required } => {
let has_floors = grid.count(|t| t.is_floor()) > 0;
has_floors == *required
}
PipelineCondition::Custom(func) => func(grid, context),
}
}
}
#[derive(Debug, Clone)]
pub struct PipelineContext {
parameters: HashMap<String, String>,
execution_log: Vec<String>,
iteration_count: usize,
grids: HashMap<String, Grid<Tile>>,
}
impl PipelineContext {
pub fn new() -> Self {
Self {
parameters: HashMap::new(),
execution_log: Vec::new(),
iteration_count: 0,
grids: HashMap::new(),
}
}
pub fn set_parameter(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.parameters.insert(key.into(), value.into());
}
pub fn get_parameter(&self, key: &str) -> Option<&String> {
self.parameters.get(key)
}
pub fn log_execution(&mut self, stage: impl Into<String>) {
self.execution_log.push(stage.into());
}
pub fn execution_history(&self) -> &[String] {
&self.execution_log
}
pub fn increment_iteration(&mut self) {
self.iteration_count += 1;
}
pub fn iteration_count(&self) -> usize {
self.iteration_count
}
pub fn store_grid(&mut self, key: impl Into<String>, grid: Grid<Tile>) {
self.grids.insert(key.into(), grid);
}
pub fn get_grid(&self, key: &str) -> Option<&Grid<Tile>> {
self.grids.get(key)
}
}
impl Default for PipelineContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct StageResult {
pub success: bool,
pub message: Option<String>,
pub output_parameters: HashMap<String, String>,
}
impl StageResult {
pub fn success() -> Self {
Self {
success: true,
message: None,
output_parameters: HashMap::new(),
}
}
pub fn success_with_message(message: impl Into<String>) -> Self {
Self {
success: true,
message: Some(message.into()),
output_parameters: HashMap::new(),
}
}
pub fn failure(message: impl Into<String>) -> Self {
Self {
success: false,
message: Some(message.into()),
output_parameters: HashMap::new(),
}
}
pub fn with_parameter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.output_parameters.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone)]
pub struct ParameterMap {
branch_parameters: HashMap<String, HashMap<String, String>>,
}
impl ParameterMap {
pub fn new() -> Self {
Self {
branch_parameters: HashMap::new(),
}
}
pub fn add_branch(
&mut self,
branch_name: impl Into<String>,
parameters: HashMap<String, String>,
) {
self.branch_parameters
.insert(branch_name.into(), parameters);
}
pub fn get_branch(&self, branch_name: &str) -> Option<&HashMap<String, String>> {
self.branch_parameters.get(branch_name)
}
pub fn merge_all(&self) -> HashMap<String, String> {
let mut merged = HashMap::new();
for params in self.branch_parameters.values() {
merged.extend(params.clone());
}
merged
}
}
impl Default for ParameterMap {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum PipelineOperation {
Algorithm { name: String, seed: Option<u64> },
Effect {
name: String,
parameters: HashMap<String, String>,
},
SetParameter { key: String, value: String },
Log { message: String },
}
#[derive(Debug, Clone)]
pub struct ConditionalPipeline {
operations: Vec<ConditionalOperation>,
}
#[derive(Debug, Clone)]
pub struct ConditionalOperation {
pub operation: PipelineOperation,
pub condition: Option<PipelineCondition>,
pub if_true: Vec<ConditionalOperation>,
pub if_false: Vec<ConditionalOperation>,
}
impl ConditionalPipeline {
pub fn new() -> Self {
Self {
operations: Vec::new(),
}
}
pub fn add_operation(&mut self, operation: ConditionalOperation) {
self.operations.push(operation);
}
pub fn execute(
&self,
grid: &mut Grid<Tile>,
context: &mut PipelineContext,
rng: &mut Rng,
) -> StageResult {
for operation in &self.operations {
let result = self.execute_operation(operation, grid, context, rng);
if !result.success {
return result;
}
for (key, value) in result.output_parameters {
context.set_parameter(key, value);
}
}
StageResult::success_with_message("Pipeline executed successfully")
}
fn execute_operation(
&self,
op: &ConditionalOperation,
grid: &mut Grid<Tile>,
context: &mut PipelineContext,
rng: &mut Rng,
) -> StageResult {
let mut result = self.execute_base_operation(&op.operation, grid, context, rng);
if let Some(condition) = &op.condition {
if condition.evaluate(grid, context) {
for true_op in &op.if_true {
let branch_result = self.execute_operation(true_op, grid, context, rng);
if !branch_result.success {
return branch_result;
}
result
.output_parameters
.extend(branch_result.output_parameters);
}
} else {
for false_op in &op.if_false {
let branch_result = self.execute_operation(false_op, grid, context, rng);
if !branch_result.success {
return branch_result;
}
result
.output_parameters
.extend(branch_result.output_parameters);
}
}
}
result
}
fn execute_base_operation(
&self,
operation: &PipelineOperation,
grid: &mut Grid<Tile>,
context: &mut PipelineContext,
_rng: &mut Rng,
) -> StageResult {
match operation {
PipelineOperation::Algorithm { name, seed } => {
let use_seed = seed.unwrap_or(12345);
match ops::generate(name, grid, Some(use_seed), None) {
Ok(()) => {
context.log_execution(format!("Algorithm: {} (seed: {})", name, use_seed));
StageResult::success()
.with_parameter("last_algorithm", name.clone())
.with_parameter("last_seed", use_seed.to_string())
}
Err(err) => StageResult::failure(err.to_string()),
}
}
PipelineOperation::Effect { name, parameters } => {
let params = params_from_strings(parameters);
match ops::effect(name, grid, Some(¶ms), None) {
Ok(()) => {
context.log_execution(format!("Effect: {}", name));
StageResult::success().with_parameter("last_effect", name.clone())
}
Err(err) => StageResult::failure(err.to_string()),
}
}
PipelineOperation::SetParameter { key, value } => {
context.set_parameter(key.clone(), value.clone());
context.log_execution(format!("Set parameter: {} = {}", key, value));
StageResult::success()
}
PipelineOperation::Log { message } => {
context.log_execution(message.clone());
StageResult::success()
}
}
}
}
impl Default for ConditionalPipeline {
fn default() -> Self {
Self::new()
}
}
impl ConditionalOperation {
pub fn simple(operation: PipelineOperation) -> Self {
Self {
operation,
condition: None,
if_true: Vec::new(),
if_false: Vec::new(),
}
}
pub fn conditional(
operation: PipelineOperation,
condition: PipelineCondition,
if_true: Vec<ConditionalOperation>,
if_false: Vec<ConditionalOperation>,
) -> Self {
Self {
operation,
condition: Some(condition),
if_true,
if_false,
}
}
}
fn params_from_strings(parameters: &HashMap<String, String>) -> Params {
parameters
.iter()
.map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
.collect()
}
#[derive(Debug, Clone)]
pub struct PipelineTemplate {
pub name: String,
pub description: String,
pub parameters: HashMap<String, String>,
pub operations: Vec<ConditionalOperation>,
}
impl PipelineTemplate {
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters: HashMap::new(),
operations: Vec::new(),
}
}
pub fn with_parameter(
mut self,
key: impl Into<String>,
default_value: impl Into<String>,
) -> Self {
self.parameters.insert(key.into(), default_value.into());
self
}
pub fn with_operation(mut self, operation: ConditionalOperation) -> Self {
self.operations.push(operation);
self
}
pub fn instantiate(
&self,
custom_params: Option<HashMap<String, String>>,
) -> ConditionalPipeline {
let mut pipeline = ConditionalPipeline::new();
let mut params = self.parameters.clone();
if let Some(custom) = custom_params {
params.extend(custom);
}
for operation in &self.operations {
let substituted = self.substitute_parameters(operation, ¶ms);
pipeline.add_operation(substituted);
}
pipeline
}
fn substitute_parameters(
&self,
operation: &ConditionalOperation,
params: &HashMap<String, String>,
) -> ConditionalOperation {
let substituted_op = match &operation.operation {
PipelineOperation::Algorithm { name, seed } => {
let sub_name = self.substitute_string(name, params);
PipelineOperation::Algorithm {
name: sub_name,
seed: *seed,
}
}
PipelineOperation::Effect { name, parameters } => {
let sub_name = self.substitute_string(name, params);
let mut sub_params = HashMap::new();
for (k, v) in parameters {
sub_params.insert(k.clone(), self.substitute_string(v, params));
}
PipelineOperation::Effect {
name: sub_name,
parameters: sub_params,
}
}
PipelineOperation::SetParameter { key, value } => PipelineOperation::SetParameter {
key: key.clone(),
value: self.substitute_string(value, params),
},
PipelineOperation::Log { message } => PipelineOperation::Log {
message: self.substitute_string(message, params),
},
};
let sub_if_true: Vec<ConditionalOperation> = operation
.if_true
.iter()
.map(|op| self.substitute_parameters(op, params))
.collect();
let sub_if_false: Vec<ConditionalOperation> = operation
.if_false
.iter()
.map(|op| self.substitute_parameters(op, params))
.collect();
ConditionalOperation {
operation: substituted_op,
condition: operation.condition.clone(),
if_true: sub_if_true,
if_false: sub_if_false,
}
}
fn substitute_string(&self, input: &str, params: &HashMap<String, String>) -> String {
let mut result = input.to_string();
for (key, value) in params {
let placeholder = format!("{{{}}}", key);
result = result.replace(&placeholder, value);
}
result
}
}
#[derive(Debug, Clone)]
pub struct TemplateLibrary {
templates: HashMap<String, PipelineTemplate>,
}
impl TemplateLibrary {
pub fn new() -> Self {
let mut library = Self {
templates: HashMap::new(),
};
library.add_builtin_templates();
library
}
pub fn add_template(&mut self, template: PipelineTemplate) {
self.templates.insert(template.name.clone(), template);
}
pub fn get_template(&self, name: &str) -> Option<&PipelineTemplate> {
self.templates.get(name)
}
pub fn template_names(&self) -> Vec<&String> {
self.templates.keys().collect()
}
fn add_builtin_templates(&mut self) {
let simple_dungeon =
PipelineTemplate::new("simple_dungeon", "Basic dungeon with rooms and corridors")
.with_parameter("algorithm", "bsp")
.with_parameter("seed", "12345")
.with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
name: "{algorithm}".to_string(),
seed: Some(12345),
}))
.with_operation(ConditionalOperation::conditional(
PipelineOperation::Log {
message: "Checking floor density".to_string(),
},
PipelineCondition::Density {
min: Some(0.1),
max: Some(0.8),
},
vec![ConditionalOperation::simple(PipelineOperation::Log {
message: "Density acceptable".to_string(),
})],
vec![ConditionalOperation::simple(PipelineOperation::Log {
message: "Density out of range".to_string(),
})],
));
self.add_template(simple_dungeon);
let cave_system =
PipelineTemplate::new("cave_system", "Organic cave system with cellular automata")
.with_parameter("algorithm", "cellular")
.with_parameter("iterations", "5")
.with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
name: "{algorithm}".to_string(),
seed: Some(54321),
}))
.with_operation(ConditionalOperation::simple(
PipelineOperation::SetParameter {
key: "generation_type".to_string(),
value: "cave".to_string(),
},
));
self.add_template(cave_system);
let maze_template = PipelineTemplate::new("maze", "Perfect maze generation")
.with_parameter("algorithm", "maze")
.with_parameter("complexity", "medium")
.with_operation(ConditionalOperation::simple(PipelineOperation::Algorithm {
name: "{algorithm}".to_string(),
seed: Some(98765),
}))
.with_operation(ConditionalOperation::conditional(
PipelineOperation::Log {
message: "Checking connectivity".to_string(),
},
PipelineCondition::Connected { required: true },
vec![ConditionalOperation::simple(PipelineOperation::Log {
message: "Maze is connected".to_string(),
})],
vec![ConditionalOperation::simple(PipelineOperation::Log {
message: "Warning: Maze may have disconnected areas".to_string(),
})],
));
self.add_template(maze_template);
}
}
impl Default for TemplateLibrary {
fn default() -> Self {
Self::new()
}
}