Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Planner and its supporting data structures #5

Merged
merged 13 commits into from
Jan 30, 2025
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ edition = "2021"
description = "An automated job orchestration library to build and execute dynamic workflows"

[dependencies]
json-patch = "2.0.0"
json-patch = "3"
jsonptr = "0.6.0"
log = "0.4.25"
matchit = "0.8.4"
serde = { version = "1.0.197" }
serde_json = "1.0.120"
thiserror = "1.0.63"
thiserror = "2"

[dev-dependencies]
dedent = "0.1.1"
env_logger = "0.11.6"
tokio = { version = "1.36.0", features = ["rt", "macros", "time"] }
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ While the ideas behind planning systems go back to the 1970s, they have seen ver
Let's create a system controller for a simple counting system. Let's define a Job that operates on i32

```rust
use gustav::*;
use gustav::task::*;
use gustav::extract::{Target, Update};

/// Plus one is a job that updates a counter if it is below some target
Expand Down Expand Up @@ -126,7 +126,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// The state model
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
struct State {
counters: HashMap<String, i32>,
}
Expand Down Expand Up @@ -166,7 +166,7 @@ async fn main() {
let agent = Worker::new()
// The plus_one job is applicable to an UPDATE operation
// on any given counter
.job("/counters/:name", update(plus_one))
.job("/counters/{name}", update(plus_one))
// Initialize two counters "a" and "b" to 0
.with_state(State {counters: HashMap::from([("a".to_string(), 0), ("b".to_string(), 0)])})

Expand All @@ -192,9 +192,7 @@ On the above example, the job definition is practically identical to the one in
As programmers, we want to be able to build code by composing simpler behaviors into more complex ones. We might want to guide the planner towards a specific solution, using the primitives we already have. For instance, let's say we want to help the planner get to a solution faster as adding tasks one by one takes too much time. We want to define a `plus_two` task, that increases the counter by 2. We could create another primitive task to update the counter by two, but as programmers, we would like to reuse the code we have already defined. We can do that using methods.

```rust
use gustav::system::Context;

fn plus_two(counter: Update<State, i32>, tgt: Target<State, i32>) -> Vec<Task<i32>> {
fn plus_two(counter: Update<State, i32>, tgt: Target<State, i32>, Path(name): Path<String>) -> Vec<Task<i32>> {
if *tgt - *counter < 2 {
// Returning an empty result tells the planner
// the task is not applicable to reach the target
Expand All @@ -204,17 +202,17 @@ fn plus_two(counter: Update<State, i32>, tgt: Target<State, i32>) -> Vec<Task<i3
// A compound job returns a list of tasks that need to be executed
// to achieve a certain goal
vec![
plus_one.into_task(Context::from_target(*tgt)),
plus_one.into_task(Context::from_target(*tgt)),
plus_one.into_task(Context::new().target(*tgt).arg("name", &name)),
plus_one.into_task(Context::new().target(*tgt).arg("name", &name)),
]
}

#[tokio::main]
async fn main() {
// build our agent using the plus one job
let agent = Worker::new()
.job("/counters/:name", update(plus_one))
.job("/counters/:name", update(plus_two))
.job("/counters/{name}", update(plus_one))
.job("/counters/{name}", update(plus_two))
// Initialize two counters "a" and "b" to 0
.with_state(State {counters: HashMap::from([("a".to_string(), 0), ("b".to_string(), 0)])})

Expand Down
Loading
Loading