LinkData#

A dependency between two tasks: which end of the source task drives which end of the target task, and the working time between them. Links go in ProjectData::links. Build one with a struct literal. LinkData has no constructor and no Default, so every field is set.

Declaration#

pub struct LinkData {
    pub id: Id,
    pub source: Id,
    pub target: Id,
    pub link_type: LinkType,
    pub lag: f64,
    pub lag_unit: Option<TimeUnit>,
}

Fields#

Field Type Default Description
id Id required Link id, unique among the project's links.
source Id required Id of the predecessor task.
target Id required Id of the successor task.
link_type LinkType LinkType::Fs on the wire Which end of source drives which end of target.
lag f64 0.0 on the wire Working time between the two ends. Negative values are allowed.
lag_unit Option<TimeUnit> None Unit of lag. None uses config.duration_unit.

id#

pub id: Id

The key that results use to refer to this link, for example in Note::DroppedLink, Cycle and the critical path in Analytics.

source, target#

pub source: Id
pub target: Id

Ids of the two tasks the link connects. An id that names no task, or names an unscheduled task, doesn't fail validation. The link is dropped and reported as a Note::DroppedLink with the reason. See Links that get dropped for every case.

pub link_type: LinkType

Fs (finish-to-start), Ss, Ff or Sf. On the wire the field is named type, accepts numeric values and defaults to "fs" when omitted; see LinkType.

lag#

pub lag: f64

Working time added between the source end and the target end, in lag_unit. A negative value lets the target overlap the source. Must be a finite number. Lag is an absolute amount; a percentage of the predecessor's duration isn't supported.

lag_unit#

pub lag_unit: Option<TimeUnit>

Unit for this link's lag. None uses SchedulingConfig::duration_unit.

Note

A per-link lag_unit is a Planning Engine extension. dhtmlxGantt always reads lag in config.duration_unit, so a lag_unit equal to config.duration_unit matches gantt exactly; any other unit has no gantt counterpart.

Errors#

The snapshot is validated when it's passed to schedule or Project::solve(Some(data)). A link fails with Error::Input when:

  • its id repeats the id of an earlier link: path links[i].id
  • its lag is NaN or infinite: path links[i].lag

Validation::Repair repairs a repeated id instead of failing.

Links added or edited after the first solve go through LinkAdd and LinkUpdate.

Example#

Task 2 starts when task 1 finishes, and task 3 starts four hours after task 2 starts, while the project counts durations in days:

use plen_core::{
    schedule, DateTime, LinkData, LinkType, ProjectData, SolveOptions, TaskData, TimeUnit,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let start = DateTime::from_ymd_hm(2026, 4, 6, 8, 0).ok_or("invalid date")?;

    let data = ProjectData {
        tasks: vec![
            TaskData { start_date: Some(start), duration: Some(2.0), ..TaskData::new(1) },
            TaskData { start_date: Some(start), duration: Some(3.0), ..TaskData::new(2) },
            TaskData { start_date: Some(start), duration: Some(1.0), ..TaskData::new(3) },
        ],
        links: vec![
            LinkData {
                id: "a".into(),
                source: 1.into(),
                target: 2.into(),
                link_type: LinkType::Fs,
                lag: 0.0,
                lag_unit: None,
            },
            LinkData {
                id: "b".into(),
                source: 2.into(),
                target: 3.into(),
                link_type: LinkType::Ss,
                lag: 4.0,
                lag_unit: Some(TimeUnit::Hour),
            },
        ],
        ..Default::default()
    };

    let result = schedule(data, &SolveOptions::default())?;
    for update in &result.updates {
        println!("{update:?}");
    }
    Ok(())
}

For how link types, lag and dropped links shape the plan, see Links, constraints and summaries.