Skip to content

Commit 5333365

Browse files
Merge pull request #672 from swimos/readme
Updates the top level readme.
2 parents 664dc54 + 90e9e07 commit 5333365

File tree

8 files changed

+186
-49
lines changed

8 files changed

+186
-49
lines changed

DEVELOPMENT.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# SwimOS Development Guid
2+
3+
## Dependencies
4+
[Formatting](https://github.com/rust-lang/rustfmt): `rustup component add rustfmt`<br>
5+
[Clippy](https://github.com/rust-lang/rust-clippy): `rustup component add clippy`<br>
6+
[Tarpaulin](https://github.com/xd009642/tarpaulin) `cargo install cargo-tarpaulin`<br>
7+
8+
## Unit tests
9+
#### Basic: `cargo test`
10+
#### With coverage: `cargo tarpaulin --ignore-tests -o Html -t 300`
11+
12+
## Lint
13+
#### Manual
14+
1) `cargo fmt --all -- --check`
15+
2) `cargo clippy --all-features --workspace --all-targets -- -D warnings`
16+
17+
#### Automatic (before commit):
18+
- Install hook: `sh ./install-commit-hook.sh`
19+
- Remove hook: `sh ./remove-commit-hook.sh`
20+
21+
Note: The pre-commit hooks take a while to run all checks.

README.md

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,21 @@
33
<a href="https://www.swimos.org"><img src="https://docs.swimos.org/readme/marlin-blue.svg" align="left"></a>
44
<br><br><br><br>
55

6-
## ⚠️🚧 Warning: Project is still under construction 👷 🚧
6+
The Swim Rust SDK contains software framework for building stateful applications that can be interacted
7+
with via multiplexed streaming APIs. It is built on top of the [Tokio asynchronous runtime](https://tokio.rs/)
8+
and a Tokio runtime is required for any Swim application.
79

8-
This project is still in its early stages of development, meaning that it is not yet stable and is subject to frequent API changes.
10+
Each application consists of some number of stateful agents, each of which runs as a separate Tokio task
11+
and can be individually addressed by a URI. An agent may have both public and private state which can either
12+
be held solely in memory or, optionally, in persistent storage. The public state of the agent consists of a
13+
number of lanes, analogous to a field in a record. There are multiple kinds of lanes that, for example, lanes
14+
containing single values and those containing a map of key-value pairs.
915

10-
**USE AT YOUR OWN RISK!**
16+
The state of any lane can be observed by establishing a link to it (either from another agent instance or a
17+
dedicated client). A established link will push all updates to the state of that lane to the subscriber and
18+
will also allow the subscriber to request changes to the state (for lane kinds that support this). Links
19+
operate over a web-socket connection and are multiplexed, meaning that links to multiple lanes on the same
20+
host can share a single web-socket connection.
1121

1222
## Usage Guides
1323

@@ -17,25 +27,85 @@ This project is still in its early stages of development, meaning that it is not
1727

1828
## Examples
1929

20-
TODO
21-
## Development
30+
The following example application runs a SwimOS server that hosts a single agent route where each agent instance
31+
has single lane, called `lane`. Each time a changes is made to the lane, it will be printed on the console by the
32+
server.
33+
34+
```rust
35+
use swimos::{
36+
agent::{
37+
agent_lifecycle::HandlerContext,
38+
agent_model::AgentModel,
39+
event_handler::{EventHandler, HandlerActionExt},
40+
lanes::ValueLane,
41+
lifecycle, AgentLaneModel,
42+
},
43+
route::RoutePattern,
44+
server::{until_termination, Server, ServerBuilder},
45+
};
46+
47+
#[tokio::main]
48+
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
49+
50+
// An agent route consists of the agent definition and a lifecycle.
51+
let model = AgentModel::new(ExampleAgent::default, ExampleLifecycle.into_lifecycle());
52+
53+
let server = ServerBuilder::with_plane_name("Example Plane")
54+
.set_bind_addr("127.0.0.1:8080".parse()?) // Bind the server to this address.
55+
.add_route(RoutePattern::parse_str("/examples/{id}")?, model) // Register the agent we have defined.
56+
.build()
57+
.await?;
58+
59+
// Run the server until we terminate it with Ctrl-C.
60+
let (task, handle) = server.run();
61+
let (ctrl_c_result, server_result) = tokio::join!(until_termination(handle, None), task);
2262

23-
### Dependencies
24-
[Formatting](https://github.com/rust-lang/rustfmt): `rustup component add rustfmt`<br>
25-
[Clippy](https://github.com/rust-lang/rust-clippy): `rustup component add clippy`<br>
26-
[Tarpaulin](https://github.com/xd009642/tarpaulin) `cargo install cargo-tarpaulin`<br>
63+
ctrl_c_result?;
64+
server_result?;
65+
Ok(())
66+
}
2767

28-
### Unit tests
29-
##### Basic: `cargo test`
30-
##### With coverage: `cargo tarpaulin --ignore-tests -o Html -t 300`
68+
// Deriving the `AgentLaneModel` trait makes this type into an agent.
69+
#[derive(AgentLaneModel)]
70+
struct ExampleAgent {
71+
lane: ValueLane<i32>,
72+
}
3173

32-
### Lint
33-
##### Manual
34-
1) `cargo fmt --all -- --check`
35-
2) `cargo clippy --all-features --workspace --all-targets -- -D warnings`
74+
// Any agent type can have any number of lifecycles defined for it. A lifecycle describes
75+
// how the agent will react to events that occur as it executes.
76+
#[derive(Default, Clone, Copy)]
77+
struct ExampleLifecycle;
3678

37-
##### Automatic (before commit):
38-
- Install hook: `sh ./install-commit-hook.sh`
39-
- Remove hook: `sh ./remove-commit-hook.sh`
79+
// The `lifecycle` macro creates an method called `into_lifecycle` for the type, using the
80+
// annotated event handlers methods in the block.
81+
#[lifecycle(ExampleAgent)]
82+
impl ExampleLifecycle {
83+
84+
#[on_event(lane)]
85+
fn lane_event(
86+
&self,
87+
context: HandlerContext<ExampleAgent>,
88+
value: &i32,
89+
) -> impl EventHandler<ExampleAgent> {
90+
let n = *value;
91+
context.get_agent_uri().and_then(move |uri| {
92+
context.effect(move || {
93+
println!("Received value: {} for 'lane' on agent at URI: {}.", n, uri);
94+
})
95+
})
96+
}
97+
98+
}
99+
```
100+
101+
For example, if a Swim client sends an update, with the value `5`, to the agent at the URI `/examples/name` for the
102+
lane `lane`, an instance of `ExampleAgent`, using `ExampleLifecycle`, will be started by the server. The value of the
103+
lane will then be set to `5` and the following will be printed on the console:
104+
105+
```
106+
Received value: 5 for 'lane' on agent at URI: /examples/name.
107+
```
108+
109+
## Development
40110

41-
Note: The pre-commit hooks take a while to run all checks.
111+
See the [development guide](DEVELOPMENT.md).

example_apps/example_util/src/lib.rs

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,28 @@ use std::{
1818
net::SocketAddr,
1919
};
2020

21-
use swimos::server::ServerHandle;
21+
use swimos::server::{until_termination, ServerHandle};
2222
use tokio::{select, sync::oneshot};
2323
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
2424

2525
pub async fn manage_handle(handle: ServerHandle) {
26-
manage_handle_report(handle, None).await
26+
until_termination(handle, None)
27+
.await
28+
.expect("Failed to register interrupt handler.");
2729
}
2830
pub async fn manage_handle_report(
29-
mut handle: ServerHandle,
31+
handle: ServerHandle,
3032
bound: Option<oneshot::Sender<SocketAddr>>,
3133
) {
32-
let mut shutdown_hook = Box::pin(async {
33-
tokio::signal::ctrl_c()
34-
.await
35-
.expect("Failed to register interrupt handler.");
36-
});
37-
let print_addr = handle.bound_addr();
38-
39-
let maybe_addr = select! {
40-
_ = &mut shutdown_hook => None,
41-
maybe_addr = print_addr => maybe_addr,
42-
};
43-
44-
if let Some(addr) = maybe_addr {
45-
if let Some(tx) = bound {
34+
let f = bound.map(|tx| {
35+
let g: Box<dyn FnOnce(SocketAddr) + Send> = Box::new(move |addr| {
4636
let _ = tx.send(addr);
47-
}
48-
println!("Bound to: {}", addr);
49-
shutdown_hook.await;
50-
}
51-
52-
println!("Stopping server.");
53-
handle.stop();
37+
});
38+
g
39+
});
40+
until_termination(handle, f)
41+
.await
42+
.expect("Failed to register interrupt handler.");
5443
}
5544

5645
struct FormatMap<'a, K, V>(&'a HashMap<K, V>);

server/swimos_server_app/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ authors = ["Swim Inc. developers info@swim.ai"]
55
edition = "2021"
66

77
[features]
8-
default = []
8+
default = ["signal"]
99
rocks_store = ["swimos_rocks_store"]
1010
trust_dns = ["swimos_runtime/trust_dns"]
11+
signal = ["tokio/signal"]
1112

1213
[dependencies]
1314
futures = { workspace = true }

server/swimos_server_app/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ pub use self::{
6565
util::AgentExt,
6666
};
6767

68+
#[cfg(feature = "signal")]
69+
pub use server::wait::{until_termination, RegistrationFailed};
70+
6871
pub use error::{AmbiguousRoutes, ServerBuilderError, ServerError};
6972
pub use ratchet::deflate::{DeflateConfig, WindowBits};
7073
pub use swimos_introspection::IntrospectionConfig;

server/swimos_server_app/src/server/mod.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,56 @@ impl Server for BoxServer {
126126
self.0.run_box()
127127
}
128128
}
129+
130+
#[cfg(feature = "signal")]
131+
pub mod wait {
132+
use std::net::SocketAddr;
133+
134+
use tracing::debug;
135+
136+
use crate::ServerHandle;
137+
138+
use thiserror::Error;
139+
140+
/// Errors that can occur waiting for tha server to stop.
141+
#[derive(Debug, Error, Clone, Copy)]
142+
#[error("The Ctrl-C handler could not be installed.")]
143+
pub struct RegistrationFailed;
144+
145+
/// Register a Ctrl-C handler that will stop a server instance.
146+
///
147+
/// # Arguments
148+
/// * `server` - The server to run.
149+
/// * `bound` - If specified this will be called when the server has bound to a socket, with the address.
150+
pub async fn until_termination(
151+
mut handle: ServerHandle,
152+
bound: Option<Box<dyn FnOnce(SocketAddr) + Send>>,
153+
) -> Result<(), RegistrationFailed> {
154+
let wait_for_ctrl_c = async move {
155+
let mut result = Ok(());
156+
let mut shutdown_hook = Box::pin(async { tokio::signal::ctrl_c().await });
157+
158+
let print_addr = handle.bound_addr();
159+
160+
let maybe_addr = tokio::select! {
161+
r = &mut shutdown_hook => {
162+
result = r;
163+
None
164+
},
165+
maybe_addr = print_addr => maybe_addr,
166+
};
167+
168+
if let Some(addr) = maybe_addr {
169+
if let Some(f) = bound {
170+
f(addr);
171+
}
172+
result = shutdown_hook.await;
173+
}
174+
175+
debug!("Stopping server.");
176+
handle.stop();
177+
result
178+
};
179+
wait_for_ctrl_c.await.map_err(|_| RegistrationFailed)
180+
}
181+
}

swimos/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ swimos_utilities = { path = "../swimos_utilities", features = ["io", "text"] }
1616
swimos_api = { path = "../api/swimos_api" }
1717
swimos_model = { path = "../api/swimos_model" }
1818
swimos_recon = { path = "../api/formats/swimos_recon" }
19-
swimos_server_app = { path = "../server/swimos_server_app", optional = true }
19+
swimos_server_app = { path = "../server/swimos_server_app", optional = true, features = ["signal"]}
2020
swimos_agent = { path = "../server/swimos_agent", optional = true }
2121
swimos_agent_derive = { path = "../server/swimos_agent_derive", optional = true }
2222
swimos_remote = { path = "../runtime/swimos_remote", optional = true}

swimos/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ pub mod io {
9494
#[cfg(feature = "server")]
9595
pub mod server {
9696
pub use swimos_server_app::{
97-
BoxServer, DeflateConfig, IntrospectionConfig, RemoteConnectionsConfig, Server,
98-
ServerBuilder, ServerHandle, WindowBits,
97+
until_termination, BoxServer, DeflateConfig, IntrospectionConfig, RemoteConnectionsConfig,
98+
Server, ServerBuilder, ServerHandle, WindowBits,
9999
};
100100

101101
/// Configuration for TLS support in the server.
@@ -111,7 +111,7 @@ pub mod server {
111111
pub use swimos_remote::tls::TlsError;
112112
pub use swimos_remote::ConnectionError;
113113
pub use swimos_server_app::{
114-
AmbiguousRoutes, ServerBuilderError, ServerError, UnresolvableRoute,
114+
AmbiguousRoutes, RegistrationFailed, ServerBuilderError, ServerError, UnresolvableRoute,
115115
};
116116
}
117117
}

0 commit comments

Comments
 (0)