Supply Lanes

This page covers the specifics of Supply Lanes and does not cover the more general aspects of Lanes. For more general information about lanes, see the Lane page.

Overview

Supply Lanes have no accessible state and instead are fed values to propagate to attached uplinks. Internally, they are backed by a queue of pending messages that are guaranteed to be dispatched. A Supply Lane has the following properties:

Similar to Demand Value Lanes, values in Supply Lanes are computed using lifecycle event handlers. However, in Supply Lanes, values are directly provided to the supply function, and no lifecycle event handler can be registered.

Supply Lanes are ideal for publishing statistical events, where it is important that a client receives every incremental update, that the state clients receive is real-time, and that updates are desired as often as possible. supply invocations may happen at scheduled intervals (using timers) or after another event has been triggered by an agent (such as after another lane receives an update and its lifecycle event handler invokes supply).

The following example demonstrates using a Supply Lane as well as how the same functionality would look using a Demand Lane:

use swimos::agent::event_handler::HandlerAction;
use swimos::agent::lanes::{DemandLane, SupplyLane};
use swimos::agent::{
    agent_lifecycle::utility::HandlerContext,
    event_handler::{EventHandler, HandlerActionExt},
    lanes::ValueLane,
    lifecycle, projections, AgentLaneModel,
};

#[projections]
#[derive(AgentLaneModel)]
pub struct ExampleAgent {
    lane: ValueLane<i32>,
    supply: SupplyLane<i32>,
    demand: DemandLane<i32>,
}

#[derive(Clone)]
pub struct ExampleLifecycle;

#[lifecycle(ExampleAgent)]
impl ExampleLifecycle {
    #[on_event(lane)]
    pub fn on_event(
        &self,
        context: HandlerContext<ExampleAgent>,
        value: &i32,
    ) -> impl EventHandler<ExampleAgent> {
        let n = *value;
        context
            .supply(ExampleAgent::SUPPLY, n * 2)
            .followed_by(context.cue(ExampleAgent::DEMAND))
    }

    #[on_cue(demand)]
    pub fn on_cue(
        &self,
        context: HandlerContext<ExampleAgent>,
    ) -> impl HandlerAction<ExampleAgent, Completion = i32> {
        context.get_value(ExampleAgent::LANE).map(|n| 2 * n)
    }
}

Use cases

Supply Lanes are appropriate for scenarios where immediate data access isn’t critical, and you can accommodate the delay between linking to the lane and invoking the supply operation. Typical use cases include:

Event handler

No lifecycle event handler is provided for a Supply Lane. Values are fed to the lane using the Handler Context’s supply function.

Handler Context Operations

The HandlerContext provided as an argument to lifecycle event handlers provide a supply function for feeding a value to the Supply Lane. An example client which uses a Supply Lane for pushing log events:

use swimos::agent::lanes::SupplyLane;
use swimos::agent::{
    agent_lifecycle::utility::HandlerContext,
    event_handler::{EventHandler, HandlerActionExt},
    lanes::ValueLane,
    lifecycle, projections, AgentLaneModel,
};
use swimos_form::Form;

#[derive(Form, Clone, Debug)]
pub struct LogEntry {
    message: String,
}

#[projections]
#[derive(AgentLaneModel)]
pub struct ExampleAgent {
    state: ValueLane<i32>,
    logs: SupplyLane<LogEntry>,
}

#[derive(Clone)]
pub struct ExampleLifecycle;

#[lifecycle(ExampleAgent)]
impl ExampleLifecycle {
    #[on_event(state)]
    pub fn on_event(
        &self,
        context: HandlerContext<ExampleAgent>,
        value: &i32,
    ) -> impl EventHandler<ExampleAgent> {
        let n = *value;
        context.supply(
            ExampleAgent::LOGS,
            LogEntry {
                message: format!("{n}"),
            },
        )
    }
}

Subscription

A subscription to a Supply Lane can only be achieved via an Event Downlink. A client for the previous agent example:

use swimos_client::{BasicValueDownlinkLifecycle, DownlinkConfig, RemotePath, SwimClientBuilder};
use swimos_form::Form;

#[derive(Form, Clone, Debug)]
pub struct LogEntry {
    message: String,
}

#[tokio::main]
async fn main() {
    let (client, task) = SwimClientBuilder::default().build().await;
    let _client_task = tokio::spawn(task);
    let handle = client.handle();

    let log_path = RemotePath::new("ws://0.0.0.0:52284", "/example/1", "logs");
    let log_lifecycle = BasicValueDownlinkLifecycle::default()
        .on_event_blocking(|log: &LogEntry| println!("{log:?}"));
    let _log_downlink = handle
        .event_downlink::<LogEntry>(log_path)
        .lifecycle(log_lifecycle)
        .downlink_config(DownlinkConfig::default())
        .open()
        .await
        .expect("Failed to open downlink");

    let state_path = RemotePath::new("ws://0.0.0.0:52284", "/example/1", "state");
    let state_downlink = handle
        .value_downlink::<i32>(state_path)
        .downlink_config(DownlinkConfig::default())
        .open()
        .await
        .expect("Failed to open downlink");

    for i in 0..10 {
        state_downlink.set(i).await.unwrap()
    }

    tokio::signal::ctrl_c()
        .await
        .expect("Failed to listen for ctrl-c.");
}

Further reading: Downlinks

Try It Yourself

A standalone project that demonstrates Supply Lanes is available here.