wingfoil 3.0.1

graph based stream processing framework
Documentation
# Threading Example

This example demonstrates multi-threaded graph execution using `producer()` and `mapper()`.

- `producer()`: Runs a sub-graph on a worker thread, sending values to the main graph
- `mapper()`: Receives values, processes them on a worker thread, sends results back

The sub-graphs are wired and executed on their own dedicated threads, using channels to
send data between them.

Historical and RealTime modes are supported. In RealTime mode, data can arrive in bursts
i.e. multiple inputs may have been received since the last engine cycle, so the incoming
data is always a vector of the source data. In this example, we use the collapse method
to collapse the burst into a single (latest) value.

## Code

```rust
use log::Level::Info;
use std::rc::Rc;
use std::thread;
use std::time::Duration;
use wingfoil::*;

fn label(name: &str) -> String {
    format!("{:?} >> {:9}", thread::current().id(), name)
}

fn main() {
    env_logger::init();
    let period = Duration::from_millis(100);
    let run_mode = RunMode::RealTime;
    let run_for = RunFor::Duration(period * 6);

    let produce_graph = move || {
        let label = label("producer");
        ticker(period).count().logged(&label, Info)
    };

    let map_graph = |src: Rc<dyn Stream<Burst<u64>>>| {
        let label = label("mapper");
        src.collapse()
            .map(|x| x * 10)
            .logged(&label, Info)
    };

    producer(produce_graph)
        .collapse()
        .logged(&label("main-pre"), Info)
        .mapper(map_graph)
        .collapse()
        .logged(&label("main-post"), Info)
        .run(run_mode, run_for)
        .unwrap();
}
```

## Running

```
RUST_LOG=info cargo run --example threading
```

## RealTime Mode Output

```
[2026-01-18T11:55:05Z INFO  wingfoil] 0.000_022 ThreadId(6) >> producer  1
[2026-01-18T11:55:05Z INFO  wingfoil] 0.000_006 ThreadId(1) >> main-pre  1
[2026-01-18T11:55:05Z INFO  wingfoil] 0.000_127 ThreadId(7) >> mapper    10
[2026-01-18T11:55:05Z INFO  wingfoil] 0.000_247 ThreadId(1) >> main-post 10
[2026-01-18T11:55:05Z INFO  wingfoil] 0.100_159 ThreadId(6) >> producer  2
[2026-01-18T11:55:05Z INFO  wingfoil] 0.101_803 ThreadId(1) >> main-pre  2
[2026-01-18T11:55:05Z INFO  wingfoil] 0.101_956 ThreadId(7) >> mapper    20
[2026-01-18T11:55:05Z INFO  wingfoil] 0.101_803 ThreadId(1) >> main-post 20
[2026-01-18T11:55:06Z INFO  wingfoil] 0.200_295 ThreadId(6) >> producer  3
[2026-01-18T11:55:06Z INFO  wingfoil] 0.200_521 ThreadId(1) >> main-pre  3
[2026-01-18T11:55:06Z INFO  wingfoil] 0.200_850 ThreadId(7) >> mapper    30
[2026-01-18T11:55:06Z INFO  wingfoil] 0.200_521 ThreadId(1) >> main-post 30
[2026-01-18T11:55:06Z INFO  wingfoil] 0.300_160 ThreadId(6) >> producer  4
[2026-01-18T11:55:06Z INFO  wingfoil] 0.300_547 ThreadId(1) >> main-pre  4
[2026-01-18T11:55:06Z INFO  wingfoil] 0.300_888 ThreadId(7) >> mapper    40
[2026-01-18T11:55:06Z INFO  wingfoil] 0.300_547 ThreadId(1) >> main-post 40
[2026-01-18T11:55:06Z INFO  wingfoil] 0.409_299 ThreadId(6) >> producer  5
[2026-01-18T11:55:06Z INFO  wingfoil] 0.409_213 ThreadId(1) >> main-pre  5
[2026-01-18T11:55:06Z INFO  wingfoil] 0.409_645 ThreadId(7) >> mapper    50
[2026-01-18T11:55:06Z INFO  wingfoil] 0.409_761 ThreadId(1) >> main-post 50
[2026-01-18T11:55:06Z INFO  wingfoil] 0.502_253 ThreadId(6) >> producer  6
[2026-01-18T11:55:06Z INFO  wingfoil] 0.502_455 ThreadId(1) >> main-pre  6
[2026-01-18T11:55:06Z INFO  wingfoil] 0.502_782 ThreadId(7) >> mapper    60
[2026-01-18T11:55:06Z INFO  wingfoil] 0.503_107 ThreadId(1) >> main-post 60
[2026-01-18T11:55:06Z INFO  wingfoil] 0.601_341 ThreadId(6) >> producer  7
```

## Historical Mode Output

Log lines may appear out of order due to channel buffering between threads, but the engine time remains consistent.

```
[2026-01-18T11:58:32Z INFO  wingfoil] 0.000_000 ThreadId(6) >> producer  1
[2026-01-18T11:58:32Z INFO  wingfoil] 0.100_000 ThreadId(6) >> producer  2
[2026-01-18T11:58:32Z INFO  wingfoil] 0.200_000 ThreadId(6) >> producer  3
[2026-01-18T11:58:32Z INFO  wingfoil] 0.300_000 ThreadId(6) >> producer  4
[2026-01-18T11:58:32Z INFO  wingfoil] 0.400_000 ThreadId(6) >> producer  5
[2026-01-18T11:58:32Z INFO  wingfoil] 0.500_000 ThreadId(6) >> producer  6
[2026-01-18T11:58:32Z INFO  wingfoil] 0.600_000 ThreadId(6) >> producer  7
[2026-01-18T11:58:32Z INFO  wingfoil] 0.700_000 ThreadId(6) >> producer  8
[2026-01-18T11:58:32Z INFO  wingfoil] 0.000_000 ThreadId(1) >> main-pre  1
[2026-01-18T11:58:32Z INFO  wingfoil] 0.000_000 ThreadId(7) >> mapper    10
[2026-01-18T11:58:32Z INFO  wingfoil] 0.000_000 ThreadId(1) >> main-post 10
[2026-01-18T11:58:32Z INFO  wingfoil] 0.100_000 ThreadId(1) >> main-pre  2
[2026-01-18T11:58:32Z INFO  wingfoil] 0.100_000 ThreadId(7) >> mapper    20
[2026-01-18T11:58:32Z INFO  wingfoil] 0.100_000 ThreadId(1) >> main-post 20
[2026-01-18T11:58:32Z INFO  wingfoil] 0.200_000 ThreadId(1) >> main-pre  3
[2026-01-18T11:58:32Z INFO  wingfoil] 0.200_000 ThreadId(7) >> mapper    30
[2026-01-18T11:58:32Z INFO  wingfoil] 0.200_000 ThreadId(1) >> main-post 30
[2026-01-18T11:58:32Z INFO  wingfoil] 0.300_000 ThreadId(1) >> main-pre  4
[2026-01-18T11:58:32Z INFO  wingfoil] 0.300_000 ThreadId(7) >> mapper    40
[2026-01-18T11:58:32Z INFO  wingfoil] 0.300_000 ThreadId(1) >> main-post 40
[2026-01-18T11:58:32Z INFO  wingfoil] 0.400_000 ThreadId(1) >> main-pre  5
[2026-01-18T11:58:32Z INFO  wingfoil] 0.400_000 ThreadId(7) >> mapper    50
[2026-01-18T11:58:32Z INFO  wingfoil] 0.400_000 ThreadId(1) >> main-post 50
[2026-01-18T11:58:32Z INFO  wingfoil] 0.500_000 ThreadId(1) >> main-pre  6
[2026-01-18T11:58:32Z INFO  wingfoil] 0.500_000 ThreadId(7) >> mapper    60
[2026-01-18T11:58:32Z INFO  wingfoil] 0.500_000 ThreadId(1) >> main-post 60
[2026-01-18T11:58:32Z INFO  wingfoil] 0.600_000 ThreadId(1) >> main-pre  7
[2026-01-18T11:58:32Z INFO  wingfoil] 0.600_000 ThreadId(7) >> mapper    70
[2026-01-18T11:58:32Z INFO  wingfoil] 0.600_000 ThreadId(1) >> main-post 70
```