Skip to content

Graph

ForgeGraph is the central data model in plant_forge. It describes the structure of a schema-based simulation object as a directed acyclic graph (DAG) of typed nodes and edges. The compiler validates it and resolves dependency ordering before running generators.

Types

ForgeGraph

pub struct ForgeGraph {
    pub graph_id: String,
    pub nodes: Vec<ForgeNode>,
    pub edges: Vec<ForgeEdge>,
}

graph_id is typically the spec identifier for the object being compiled (e.g. the region id or area id). Node and edge lists may be empty for trivially simple kinds.

ForgeNode

pub struct ForgeNode {
    pub id: String,
    pub kind: ForgeNodeKind,
    pub properties: serde_json::Value,
}

Each node has a stable string id unique within its graph. properties is an open JSON object for domain-specific metadata that the compile step preserves and passes through to the compiled output.

ForgeNodeKind

pub enum ForgeNodeKind {
    Anchor,
    Generator,
    Field,
    Institution,
}
Kind Meaning
Anchor Fixed reference point; other nodes depend on it
Generator Runs a JTL program to populate dependent nodes
Field A named property or data field produced by generators
Institution A named categorical grouping (e.g. faction, biome class)

These are the default vocabulary. Future ports may not use all four; the compiler operates on any mix.

ForgeEdge

pub struct ForgeEdge {
    pub id: String,
    pub kind: ForgeEdgeKind,
    pub from: String,
    pub to: String,
    pub properties: serde_json::Value,
}

from and to are node ids within the same graph. id must be unique within the graph.

ForgeEdgeKind

pub enum ForgeEdgeKind {
    Constraint,
    Produces,
    Feeds,
    Governs,
    Interacts,
}
Kind Dependency Edge Meaning
Constraint yes from must be compiled before to
Produces yes from generates output consumed by to
Feeds no Data flow hint; does not affect compile ordering
Governs no Authority / ownership relationship
Interacts no Bidirectional influence; no ordering implication

Only Constraint and Produces edges participate in topological ordering. The others are carried through to compiled output for downstream use.

Validation

ForgeGraph::validate() runs four checks:

  1. Unique node ids — no two nodes share an id.
  2. Unique edge ids — no two edges share an id.
  3. No dangling edges — every from and to references an existing node id.
  4. No dependency cycles — the subgraph of Constraint and Produces edges is acyclic (verified by Kahn's algorithm).
pub enum GraphValidationError {
    DuplicateNodeId(String),
    DuplicateEdgeId(String),
    DanglingFrom { edge_id: String, node_id: String },
    DanglingTo  { edge_id: String, node_id: String },
    DependencyCycle,
}

compile_forge() calls validate() internally and returns an error on the first violation. Callers should not pass unvalidated graphs to the compiler.

Building a ForgeGraph

The standard pattern is a to_forge_graph(&self) -> ForgeGraph method on your spec type:

impl RegionGraphSpec {
    pub fn to_forge_graph(&self) -> ForgeGraph {
        let nodes = self.nodes.iter().map(|n| ForgeNode {
            id: n.node_id.clone(),
            kind: ForgeNodeKind::from(&n.kind),
            properties: serde_json::to_value(&n.params).unwrap_or_default(),
        }).collect();

        let edges = self.edges.iter().map(|e| ForgeEdge {
            id: e.edge_id.clone(),
            kind: ForgeEdgeKind::from(&e.kind),
            from: e.from.clone(),
            to: e.to.clone(),
            properties: serde_json::Value::Null,
        }).collect();

        ForgeGraph {
            graph_id: self.region_id.clone(),
            nodes,
            edges,
        }
    }
}

See the Forge Port Guide for the full region reference implementation.