Began work on checkers, changed scoped-value to dashed
This commit is contained in:
1 parent
e5301f7f49
commit
83aac69252
14 files changed
+3165
-37
No files matched your search
Generated
+2960
-34
File diff suppressed because it is too large.
Load diff
+3
-1
@@ -11,13 +11,15 @@ members = [
|
||||
"orchid-api",
|
||||
"orchid-api-derive",
|
||||
"orchid-api-traits",
|
||||
"scoped-value",
|
||||
"stdio-perftest",
|
||||
"xtask",
|
||||
"async-fn-stream",
|
||||
"unsync-pipe",
|
||||
"orchid-async-utils",
|
||||
"checklings",
|
||||
"scoped_value",
|
||||
"checkers-web",
|
||||
"checkers-api",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "checkers-api"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.14.0"
|
||||
actix-ws = "0.4.0"
|
||||
async-event = "0.2.1"
|
||||
futures = "0.3.32"
|
||||
@@ -0,0 +1 @@
|
||||
For a description of Checkers, go to [../checkers-web]
|
||||
@@ -0,0 +1,124 @@
|
||||
use std::collections::HashMap;
|
||||
use std::pin::pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::{App, Error, HttpRequest, HttpResponse, HttpServer, Responder, get, post, rt, web};
|
||||
use actix_ws::{AggregatedMessage, ProtocolError};
|
||||
use async_event::Event;
|
||||
use futures::lock::Mutex;
|
||||
use futures::stream::StreamExt as _;
|
||||
use futures::stream_select;
|
||||
|
||||
struct Message {
|
||||
data: web::Bytes,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MessageStream {
|
||||
backlog: Mutex<Vec<Message>>,
|
||||
pushed: Event,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AppState {
|
||||
streams: Mutex<HashMap<String, Arc<MessageStream>>>,
|
||||
}
|
||||
|
||||
#[get("/read/{topic}")]
|
||||
async fn read(
|
||||
topic: String,
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
state: web::Data<AppState>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let (res, mut session, stream) = actix_ws::handle(&req, stream)?;
|
||||
|
||||
let topic = state.streams.lock().await.entry(topic).or_insert_with(Arc::default).clone();
|
||||
|
||||
let stream = stream
|
||||
.aggregate_continuations()
|
||||
// aggregate continuation frames up to 1MiB
|
||||
.max_continuation_size(2_usize.pow(20));
|
||||
|
||||
// start task but don't wait for it
|
||||
rt::spawn(async move {
|
||||
let backlog_g = topic.backlog.lock().await;
|
||||
let mut cursor = backlog_g.len();
|
||||
for message in backlog_g.iter() {
|
||||
session.binary(message.data.clone()).await.unwrap();
|
||||
}
|
||||
std::mem::drop(backlog_g);
|
||||
|
||||
enum Change {
|
||||
Message(Result<AggregatedMessage, ProtocolError>),
|
||||
Ingress,
|
||||
}
|
||||
|
||||
let stream = pin!(stream.map(Change::Message));
|
||||
|
||||
let data_changes = pin!(futures::stream::unfold(topic.clone(), async |topic| {
|
||||
topic.pushed.wait_until(|| Some(())).await;
|
||||
Some((Change::Ingress, topic))
|
||||
}));
|
||||
|
||||
let mut events = stream_select!(stream, data_changes);
|
||||
|
||||
while let Some(ev) = events.next().await {
|
||||
match ev {
|
||||
Change::Message(Err(e)) => {
|
||||
eprintln!("{e}");
|
||||
session.close(None).await.unwrap();
|
||||
break;
|
||||
},
|
||||
Change::Message(Ok(AggregatedMessage::Ping(msg))) => session.pong(&msg).await.unwrap(),
|
||||
Change::Message(Ok(_)) => {
|
||||
session
|
||||
.close(Some(actix_ws::CloseReason {
|
||||
code: actix_ws::CloseCode::Normal,
|
||||
description: Some("User requested shutdown by sending message".to_string()),
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
break;
|
||||
},
|
||||
Change::Ingress => {
|
||||
let backlog_g = topic.backlog.lock().await;
|
||||
for msg in &backlog_g[cursor..] {
|
||||
session.binary(msg.data.clone()).await.unwrap();
|
||||
}
|
||||
cursor = backlog_g.len();
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// respond immediately with response connected to WS session
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[post("/push/{topic}")]
|
||||
async fn push(topic: String, mut body: web::Payload, state: web::Data<AppState>) -> impl Responder {
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = body.next().await {
|
||||
data.extend_from_slice(&chunk?);
|
||||
}
|
||||
let topic = state.streams.lock().await.entry(topic).or_insert_with(Arc::default).clone();
|
||||
topic.backlog.lock().await.push(Message { data: data.into() });
|
||||
topic.pushed.notify_all();
|
||||
Ok::<_, actix_web::error::PayloadError>(())
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let custom_path_prefix = std::env::var("CHECKERS_API_PATH");
|
||||
let path_prefix = custom_path_prefix.map(|s| s.to_owned()).unwrap_or("".to_string());
|
||||
HttpServer::new(move || App::new().service(web::scope(&path_prefix).service(push).service(read)))
|
||||
.bind((
|
||||
std::env::var("CHECKERS_API_HOST").as_deref().unwrap_or("127.0.0.1"),
|
||||
std::env::var("CHECKERS_API_PORT")
|
||||
.map(|s| s.parse::<u16>().expect("Could not parse custom port"))
|
||||
.unwrap_or(8170),
|
||||
))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "checkers-web"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
dioxus = { version = "0.7.1", features = [] }
|
||||
|
||||
[features]
|
||||
default = ["web"]
|
||||
web = ["dioxus/web"]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Development
|
||||
|
||||
The web crate defines the entrypoint for the web app along with any assets, components and dependencies that are specific to web builds. The web crate starts out something like this:
|
||||
|
||||
```
|
||||
web/
|
||||
├─ assets/ # Assets used by the web app - Any platform specific assets should go in this folder
|
||||
├─ src/
|
||||
│ ├─ main.rs # The entrypoint for the web app.
|
||||
├─ Cargo.toml # The web crate's Cargo.toml - This should include all web specific dependencies
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
This crate will only be included in the web build, so you should add all web specific dependencies to this crate's [Cargo.toml](../Cargo.toml) file instead of the shared [ui](../ui/Cargo.toml) crate.
|
||||
|
||||
### Serving Your Web App
|
||||
|
||||
You can start your web app with the following command:
|
||||
|
||||
```bash
|
||||
dx serve
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
body {
|
||||
background-color: #0f1116;
|
||||
color: #ffffff;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const MAIN_CSS: Asset = asset!("/assets/main.css");
|
||||
|
||||
fn main() { dioxus::launch(App); }
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
// Build cool things ✌️
|
||||
|
||||
rsx! {
|
||||
// Global app resources
|
||||
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||
p { "Hello, World!" }
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
scoped_value = { version = "1.0.0", path = "../scoped_value" }
|
||||
scoped-value = { path = "../scoped-value", version = "1.0.0" }
|
||||
@@ -1,2 +1,10 @@
|
||||
type-complexity-threshold = 300
|
||||
avoid-breaking-exported-api = false
|
||||
await-holding-invalid-types = [
|
||||
"generational_box::GenerationalRef",
|
||||
{ path = "generational_box::GenerationalRef", reason = "Reads should not be held over an await point. This will cause any writes to fail while the await is pending since the read borrow is still active." },
|
||||
"generational_box::GenerationalRefMut",
|
||||
{ path = "generational_box::GenerationalRefMut", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." },
|
||||
"dioxus_signals::WriteLock",
|
||||
{ path = "dioxus_signals::WriteLock", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." },
|
||||
]
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "scoped_value"
|
||||
name = "scoped-value"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
description = "An extension trait for scoping thread locals to a future"
|
||||
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user