Skip to content

Commit b527ea2

Browse files
committed
Merge branch 'main' into docs2
2 parents 00f9b09 + 905d3b4 commit b527ea2

File tree

10 files changed

+257
-11
lines changed

10 files changed

+257
-11
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ members = [
3737
"example_apps/tutorial_app",
3838
"example_apps/tutorial_app/model",
3939
"example_apps/tutorial_app/generator",
40+
"example_apps/join_value",
4041
]
4142

4243
exclude = [

api/swimos_agent_protocol/src/ad_hoc/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::AdHocCommand;
2929
mod tests;
3030

3131
#[derive(Debug, Default, Clone, Copy)]
32-
pub struct CommandEncoder<E> {
32+
struct CommandEncoder<E> {
3333
body_encoder: E,
3434
}
3535

api/swimos_agent_protocol/src/lib.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub mod encoding {
8787
};
8888
}
8989

90-
/// # The encoding used for map like lanes and stores, shared between the other protocols in this module.
90+
/// The encoding used for map like lanes and stores, shared between the other protocols in this module.
9191
pub mod map {
9292
pub use crate::map::{
9393
MapMessageDecoder, MapMessageEncoder, MapOperationDecoder, MapOperationEncoder,
@@ -96,7 +96,7 @@ pub mod encoding {
9696
};
9797
}
9898

99-
/// # The protocol used by the agents to send ad hoc messages to lanes on other agents.
99+
/// The protocol used by the agents to send ad hoc messages to lanes on other agents.
100100
///
101101
/// [`crate::AdHocCommand`] messages are sent by agents to the runtime to instruct it to send an ad hoc
102102
/// command to an arbitrary lane endpoint.
@@ -109,10 +109,6 @@ pub mod encoding {
109109

110110
/// # The protocol used by the runtime to communicate with stores.
111111
///
112-
/// TODO Non-transient lanes also implicitly contain a store. They should
113-
/// ultimately use the initialization component of this protocol. Currently,
114-
/// they have initialization messages built into the lane protocol.
115-
///
116112
/// There are two phases to the communication between the runtime and the agent.
117113
///
118114
/// 1. Initialization
@@ -135,6 +131,11 @@ pub mod encoding {
135131
/// 2. The store or land sends [`crate::StoreResponse`] messages each time its state
136132
/// changes which are persisted by the runtime.
137133
pub mod store {
134+
135+
// TODO Non-transient lanes also implicitly contain a store. They should
136+
// ultimately use the initialization component of this protocol. Currently,
137+
// they have initialization messages built into the lane protocol.
138+
138139
pub use crate::store::{
139140
MapStoreInitDecoder, MapStoreResponseEncoder, RawMapStoreInitDecoder,
140141
RawMapStoreInitEncoder, RawMapStoreResponseDecoder, RawValueStoreInitDecoder,

api/swimos_agent_protocol/src/model.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,42 +58,52 @@ impl<T> LaneResponse<T> {
5858
/// to describe alterations to the lane.
5959
#[derive(Copy, Clone, Debug, PartialEq, Eq, Form)]
6060
pub enum MapOperation<K, V> {
61+
/// Update the value associated with a key in the map (or insert an entry if they key does not exist).
6162
#[form(tag = "update")]
6263
Update {
6364
key: K,
6465
#[form(body)]
6566
value: V,
6667
},
68+
/// Remove an entry from the map, by key (does nothing if there is no such entry).
6769
#[form(tag = "remove")]
6870
Remove {
6971
#[form(header)]
7072
key: K,
7173
},
74+
/// Remove all entries in the map.
7275
#[form(tag = "clear")]
7376
Clear,
7477
}
7578

7679
/// Representation of map lane messages (used to form the body of Recon messages when operating)
7780
/// on downlinks. This extends [`MapOperation`] with `Take` (retain the first `n` items) and `Drop`
78-
/// (remove the first `n` items). We never use these internally but must support them for communicating
79-
/// with other implementations.
81+
/// (remove the first `n` items).
8082
#[derive(Copy, Clone, Debug, PartialEq, Eq, Form, Hash)]
8183
pub enum MapMessage<K, V> {
84+
/// Update the value associated with a key in the map (or insert an entry if they key does not exist).
8285
#[form(tag = "update")]
8386
Update {
8487
key: K,
8588
#[form(body)]
8689
value: V,
8790
},
91+
/// Remove an entry from the map, by key (does nothing if there is no such entry).
8892
#[form(tag = "remove")]
8993
Remove {
9094
#[form(header)]
9195
key: K,
9296
},
97+
/// Remove all entries in the map.
9398
#[form(tag = "clear")]
9499
Clear,
100+
/// Retain only the first `n` entries in the map, the remainder are removed. The ordering
101+
/// used to determine 'first' is the Recon order of the keys. If there are fewer than `n`
102+
/// entries in the map, this does nothing.
95103
#[form(tag = "take")]
96104
Take(#[form(header_body)] u64),
105+
/// Remove the first `n` entries in the map. The ordering used to determine 'first' is the
106+
/// Recon order of the keys. If there are fewer than `n` entries in the map, it is cleared.
97107
#[form(tag = "drop")]
98108
Drop(#[form(header_body)] u64),
99109
}
@@ -148,7 +158,8 @@ impl<S, T> AdHocCommand<S, T> {
148158
/// * `command` - The body of the command message.
149159
/// * `overwrite_permitted` - Controls the behaviour of command handling in the case of back-pressure.
150160
/// If this is true, the command maybe be overwritten by a subsequent command to the same target (and so
151-
/// will never be sent). If false, the command will be queued instead.
161+
/// will never be sent). If false, the command will be queued instead. This is a user specifiable parameter
162+
/// in the API.
152163
pub fn new(address: Address<S>, command: T, overwrite_permitted: bool) -> Self {
153164
AdHocCommand {
154165
address,

api/swimos_api/src/agent/lane/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#[cfg(test)]
16+
mod tests;
17+
1518
use std::{
1619
borrow::Borrow,
1720
fmt::{Display, Formatter},
@@ -72,7 +75,10 @@ impl WarpLaneKind {
7275

7376
pub fn uplink_kind(&self) -> UplinkKind {
7477
match self {
75-
WarpLaneKind::Map | WarpLaneKind::DemandMap | WarpLaneKind::JoinMap => UplinkKind::Map,
78+
WarpLaneKind::Map
79+
| WarpLaneKind::DemandMap
80+
| WarpLaneKind::JoinMap
81+
| WarpLaneKind::JoinValue => UplinkKind::Map,
7682
WarpLaneKind::Supply => UplinkKind::Supply,
7783
WarpLaneKind::Spatial => todo!("Spatial uplinks not supported."),
7884
_ => UplinkKind::Value,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::agent::UplinkKind;
2+
3+
use super::WarpLaneKind;
4+
5+
// spatial lanes are omitted as they are unimplemented
6+
const LANE_KINDS: [WarpLaneKind; 8] = [
7+
WarpLaneKind::Command,
8+
WarpLaneKind::Demand,
9+
WarpLaneKind::DemandMap,
10+
WarpLaneKind::Map,
11+
WarpLaneKind::JoinMap,
12+
WarpLaneKind::JoinValue,
13+
WarpLaneKind::Supply,
14+
WarpLaneKind::Value,
15+
];
16+
17+
#[test]
18+
fn uplink_kinds() {
19+
for kind in LANE_KINDS {
20+
let uplink_kind = kind.uplink_kind();
21+
if kind.map_like() {
22+
assert_eq!(uplink_kind, UplinkKind::Map);
23+
} else if matches!(kind, WarpLaneKind::Supply) {
24+
assert_eq!(uplink_kind, UplinkKind::Supply);
25+
} else {
26+
assert_eq!(uplink_kind, UplinkKind::Value)
27+
}
28+
}
29+
}
30+
31+
// this is here for when spatial lanes are implemented as the test will no longer panic
32+
#[test]
33+
#[should_panic]
34+
fn spatial_uplink_kind() {
35+
WarpLaneKind::Spatial.uplink_kind();
36+
}

example_apps/join_value/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "join_value"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
swimos = { path = "../../swimos", features = ["server", "agent"] }
8+
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
9+
example-util = { path = "../example_util" }
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use std::collections::HashMap;
2+
use swimos::agent::agent_lifecycle::utility::HandlerContext;
3+
use swimos::agent::event_handler::{EventHandler, HandlerActionExt};
4+
use swimos::agent::lanes::{CommandLane, JoinValueLane};
5+
use swimos::agent::projections;
6+
use swimos::agent::{lifecycle, AgentLaneModel};
7+
8+
#[derive(AgentLaneModel)]
9+
#[projections]
10+
pub struct BuildingAgent {
11+
lights: JoinValueLane<u64, bool>,
12+
register_room: CommandLane<u64>,
13+
}
14+
15+
#[derive(Clone)]
16+
pub struct BuildingLifecycle;
17+
18+
#[lifecycle(BuildingAgent)]
19+
impl BuildingLifecycle {
20+
#[on_start]
21+
pub fn on_start(
22+
&self,
23+
context: HandlerContext<BuildingAgent>,
24+
) -> impl EventHandler<BuildingAgent> {
25+
context
26+
.get_agent_uri()
27+
.and_then(move |uri| context.effect(move || println!("Starting agent at: {}", uri)))
28+
}
29+
30+
#[on_stop]
31+
pub fn on_stop(
32+
&self,
33+
context: HandlerContext<BuildingAgent>,
34+
) -> impl EventHandler<BuildingAgent> {
35+
context
36+
.get_agent_uri()
37+
.and_then(move |uri| context.effect(move || println!("Stopping agent at: {}", uri)))
38+
}
39+
40+
#[on_command(register_room)]
41+
pub fn register_room(
42+
&self,
43+
context: HandlerContext<BuildingAgent>,
44+
room_id: &u64,
45+
) -> impl EventHandler<BuildingAgent> {
46+
let room_id = *room_id;
47+
context
48+
.get_parameter("name")
49+
.and_then(move |building_name: Option<String>| {
50+
let building_name = building_name.expect("Missing building name URI parameter");
51+
context.add_downlink(
52+
BuildingAgent::LIGHTS,
53+
room_id,
54+
None,
55+
format!("/rooms/{building_name}/{room_id}").as_str(),
56+
"lights",
57+
)
58+
})
59+
.followed_by(context.effect(move || println!("Registered room: {room_id}")))
60+
}
61+
62+
#[on_update(lights)]
63+
fn lights(
64+
&self,
65+
context: HandlerContext<BuildingAgent>,
66+
_map: &HashMap<u64, bool>,
67+
key: u64,
68+
prev: Option<bool>,
69+
new_value: &bool,
70+
) -> impl EventHandler<BuildingAgent> {
71+
let new_value = *new_value;
72+
context.effect(move || println!("Light {key} changed from {prev:?} to {new_value}"))
73+
}
74+
}

example_apps/join_value/src/main.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2015-2023 Swim Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::{error::Error, time::Duration};
16+
17+
use crate::{
18+
building::{BuildingAgent, BuildingLifecycle},
19+
room::{RoomAgent, RoomLifecycle},
20+
};
21+
use example_util::{example_logging, manage_handle};
22+
use swimos::{
23+
agent::agent_model::AgentModel, route::RoutePattern, server::Server, server::ServerBuilder,
24+
};
25+
26+
mod building;
27+
mod room;
28+
29+
#[tokio::main]
30+
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
31+
example_logging()?;
32+
33+
let building_route = RoutePattern::parse_str("/buildings/:name")?;
34+
let building_agent =
35+
AgentModel::new(BuildingAgent::default, BuildingLifecycle.into_lifecycle());
36+
37+
let room_route = RoutePattern::parse_str("/rooms/:building/:room")?;
38+
let room_agent = AgentModel::new(RoomAgent::default, RoomLifecycle.into_lifecycle());
39+
40+
let server = ServerBuilder::with_plane_name("Building Plane")
41+
.add_route(building_route, building_agent)
42+
.add_route(room_route, room_agent)
43+
.update_config(|config| {
44+
config.agent_runtime.inactive_timeout = Duration::from_secs(5 * 60);
45+
})
46+
.build()
47+
.await?;
48+
49+
let (task, handle) = server.run();
50+
let shutdown = manage_handle(handle);
51+
let (_, result) = tokio::join!(shutdown, task);
52+
53+
result?;
54+
println!("Server stopped successfully.");
55+
Ok(())
56+
}

example_apps/join_value/src/room.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use std::str::FromStr;
2+
use swimos::agent::agent_lifecycle::utility::HandlerContext;
3+
use swimos::agent::event_handler::{EventHandler, HandlerActionExt};
4+
use swimos::agent::lanes::ValueLane;
5+
use swimos::agent::projections;
6+
use swimos::agent::{lifecycle, AgentLaneModel};
7+
8+
#[derive(AgentLaneModel)]
9+
#[projections]
10+
pub struct RoomAgent {
11+
lights: ValueLane<bool>,
12+
}
13+
14+
#[derive(Clone)]
15+
pub struct RoomLifecycle;
16+
17+
#[lifecycle(RoomAgent)]
18+
impl RoomLifecycle {
19+
#[on_start]
20+
pub fn on_start(&self, context: HandlerContext<RoomAgent>) -> impl EventHandler<RoomAgent> {
21+
context
22+
.get_agent_uri()
23+
.and_then(move |uri| context.effect(move || println!("Starting agent at: {}", uri)))
24+
.followed_by(context.get_parameter("building").and_then(
25+
move |building_name: Option<String>| {
26+
context
27+
.get_parameter("room")
28+
.and_then(move |room_id: Option<String>| {
29+
let building_name =
30+
building_name.expect("Missing building URI parameter");
31+
let room_id_str = room_id.expect("Missing room ID URI parameter");
32+
let room_id = u64::from_str(room_id_str.as_str())
33+
.expect("Expected a u64 room ID");
34+
35+
context.send_command(
36+
None,
37+
format!("/buildings/{building_name}"),
38+
"register_room".to_string(),
39+
room_id,
40+
)
41+
})
42+
},
43+
))
44+
}
45+
46+
#[on_stop]
47+
pub fn on_stop(&self, context: HandlerContext<RoomAgent>) -> impl EventHandler<RoomAgent> {
48+
context
49+
.get_agent_uri()
50+
.and_then(move |uri| context.effect(move || println!("Starting agent at: {}", uri)))
51+
}
52+
}

0 commit comments

Comments
 (0)