Skip to content

Commit 51b7909

Browse files
authored
Merge pull request #613 from swimos/docs_devguide_2.2
Example code for dev guide 2.2
2 parents 0a4851e + 18ac469 commit 51b7909

File tree

7 files changed

+183
-1
lines changed

7 files changed

+183
-1
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88
!/demos/**/Cargo.lock
99

1010
**/.DS_Store
11-
*.iml
11+
*.iml
12+
13+
# Code coverage files
14+
*.profraw

.tarpaulin.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ exclude = [
3232
"swimos_form_derive",
3333
"swimos_agent_derive",
3434
"macro_utilities",
35+
"example_client_2_2",
36+
"example_server_2_2"
3537
]
3638
workspace = true
3739
avoid-cfg-tarpaulin = true

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ members = [
4040
"example_apps/join_value",
4141
"example_apps/aggregations",
4242
"example_apps/time_series",
43+
"example_apps/devguide/2_2/*",
4344
]
4445

4546
exclude = [
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "example_client_2_2"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
tokio = { workspace = true, features = ["full"] }
8+
swimos_client = { path = "../../../../client/swimos_client" }
9+
swimos_form = { path = "../../../../api/swimos_form" }
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use swimos_client::{BasicValueDownlinkLifecycle, DownlinkConfig, RemotePath, SwimClientBuilder};
2+
3+
#[tokio::main]
4+
async fn main() {
5+
// Build a Swim Client using the default configuration.
6+
// The `build` method returns a `SwimClient` instance and its internal
7+
// runtime future that is spawned below.
8+
let (client, task) = SwimClientBuilder::default().build().await;
9+
let _client_task = tokio::spawn(task);
10+
let handle = client.handle();
11+
12+
// Build a path the downlink.
13+
let state_path = RemotePath::new(
14+
// The host address
15+
"ws://0.0.0.0:8080",
16+
// You can provide any agent URI that matches the pattern
17+
// "/example/:id"
18+
"/example/1",
19+
// This is the URI of the ValueLane<i32> in our ExampleAgent
20+
"state",
21+
);
22+
23+
let lifecycle = BasicValueDownlinkLifecycle::<usize>::default()
24+
// Register an event handler that is invoked when the downlink connects to the agent.
25+
.on_linked_blocking(|| println!("Downlink linked"))
26+
// Register an event handler that is invoked when the downlink synchronises its state.
27+
// with the agent.
28+
.on_synced_blocking(|value| println!("Downlink synced with: {value:?}"))
29+
// Register an event handler that is invoked when the downlink receives an event.
30+
.on_event_blocking(|value| println!("Downlink event: {value:?}"));
31+
32+
// Build our downlink.
33+
//
34+
// This operation may fail if there is a connection issue.
35+
let state_downlink = handle
36+
.value_downlink::<i32>(state_path)
37+
.lifecycle(lifecycle)
38+
.downlink_config(DownlinkConfig::default())
39+
.open()
40+
.await
41+
.expect("Failed to open downlink");
42+
43+
for i in 0..10 {
44+
// Update the lane's state.
45+
state_downlink
46+
.set(i)
47+
.await
48+
.expect("Failed to set downlink state");
49+
}
50+
51+
tokio::signal::ctrl_c()
52+
.await
53+
.expect("Failed to listen for ctrl-c.");
54+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "example_server_2_2"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
tokio = { workspace = true, features = ["full"] }
8+
swimos = { path = "../../../../swimos", features = ["server"] }
9+
swimos_form = { path = "../../../../api/swimos_form" }
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use swimos::{
2+
agent::{
3+
agent_lifecycle::HandlerContext, agent_model::AgentModel, event_handler::EventHandler,
4+
lanes::ValueLane, lifecycle, projections, AgentLaneModel,
5+
},
6+
route::RoutePattern,
7+
server::{Server, ServerBuilder, ServerHandle},
8+
};
9+
10+
use std::{error::Error, time::Duration};
11+
12+
#[derive(AgentLaneModel)]
13+
#[projections]
14+
pub struct ExampleAgent {
15+
state: ValueLane<i32>,
16+
}
17+
18+
#[derive(Clone)]
19+
pub struct ExampleLifecycle;
20+
21+
#[lifecycle(ExampleAgent)]
22+
impl ExampleLifecycle {
23+
// Handler invoked when the agent starts.
24+
#[on_start]
25+
pub fn on_start(
26+
&self,
27+
context: HandlerContext<ExampleAgent>,
28+
) -> impl EventHandler<ExampleAgent> {
29+
context.effect(|| println!("Starting agent."))
30+
}
31+
32+
// Handler invoked when the agent is about to stop.
33+
#[on_stop]
34+
pub fn on_stop(
35+
&self,
36+
context: HandlerContext<ExampleAgent>,
37+
) -> impl EventHandler<ExampleAgent> {
38+
context.effect(|| println!("Stopping agent."))
39+
}
40+
41+
// Handler invoked after the state of 'lane' has changed.
42+
#[on_event(state)]
43+
pub fn on_event(
44+
&self,
45+
context: HandlerContext<ExampleAgent>,
46+
value: &i32,
47+
) -> impl EventHandler<ExampleAgent> {
48+
let n = *value;
49+
// EventHandler::effect accepts a FnOnce()
50+
// which runs a side effect.
51+
context.effect(move || {
52+
println!("Setting value to: {}", n);
53+
})
54+
}
55+
}
56+
57+
#[tokio::main]
58+
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
59+
// Create a dynamic route for our agents.
60+
let route = RoutePattern::parse_str("/example/:id")?;
61+
// Create an agent model which contains the factory for creating the agent as well
62+
// as the lifecycle which will be run.
63+
let agent = AgentModel::new(ExampleAgent::default, ExampleLifecycle.into_lifecycle());
64+
65+
// Create a server builder.
66+
let server = ServerBuilder::with_plane_name("Plane")
67+
// Bind to port 8080
68+
.set_bind_addr("127.0.0.1:8080".parse().unwrap())
69+
// For this guide, ensure agents timeout fairly quickly.
70+
// An agent will timeout after they have received no new updates
71+
// for this configured period of time.
72+
.update_config(|config| {
73+
config.agent_runtime.inactive_timeout = Duration::from_secs(20);
74+
})
75+
// Register the agent against the route.
76+
.add_route(route, agent)
77+
.build()
78+
// Building the server may fail if many routes are registered and some
79+
// are ambiguous.
80+
.await?;
81+
82+
// Run the server. A tuple of the server's runtime
83+
// future and a handle to the runtime is returned.
84+
let (task, handle) = server.run();
85+
// Watch for ctrl+c signals
86+
let shutdown = manage_handle(handle);
87+
88+
// Join on the server and ctrl+c futures.
89+
let (_, result) = tokio::join!(shutdown, task);
90+
91+
result?;
92+
println!("Server stopped successfully.");
93+
Ok(())
94+
}
95+
96+
// Utility function for awaiting a stop signal in the terminal.
97+
async fn manage_handle(mut handle: ServerHandle) {
98+
tokio::signal::ctrl_c()
99+
.await
100+
.expect("Failed to register interrupt handler.");
101+
102+
println!("Stopping server.");
103+
handle.stop();
104+
}

0 commit comments

Comments
 (0)