P1a: material import, sixteen faces, sample engine and quarry

This commit is contained in:
Emil
2026-11-01 00:56:51 +03:00
parent db4368efae
commit ebdeca3b92
28 changed files with 2799 additions and 114 deletions
+1
View File
@@ -11,6 +11,7 @@ path = "src/main.rs"
[dependencies]
zhila-core = { path = "../zhila-core" }
zhila-engine = { path = "../zhila-engine" }
zhila-material = { path = "../zhila-material" }
zhila-protocol = { path = "../zhila-protocol" }
tokio.workspace = true
tokio-tungstenite.workspace = true
+71 -5
View File
@@ -20,7 +20,7 @@ use tracing::{info, warn};
use zhila_core::{CoreError, Engine, Session};
use zhila_engine::{AudioEngine, NullEngine};
use zhila_protocol::{
Actor, ClientMessage, CommandResult, ErrorCode, PROTOCOL_VERSION, ServerMessage,
Actor, ClientMessage, CommandResult, ErrorCode, PROTOCOL_VERSION, Seal, ServerMessage,
};
const HELLO_TIMEOUT: Duration = Duration::from_secs(5);
@@ -216,7 +216,7 @@ async fn handle(stream: TcpStream, shared: Shared) -> anyhow::Result<()> {
protocol,
token,
} => (client, protocol, token),
ClientMessage::Command { .. } => {
ClientMessage::Command { .. } | ClientMessage::ImportCore { .. } => {
let _ = send_out(
&mut sink,
error_message(None, ErrorCode::Protocol, "expected hello before commands"),
@@ -303,9 +303,60 @@ async fn handle(stream: TcpStream, shared: Shared) -> anyhow::Result<()> {
}
});
let mut pending_import: Option<(u64, String, Seal)> = None;
while let Some(frame) = source.next().await {
let message = match frame {
Ok(Message::Text(text)) => text,
Ok(Message::Binary(bytes)) => {
let Some((id, name, seal)) = pending_import.take() else {
let _ = direct_tx.send(error_message(
None,
ErrorCode::BadRequest,
"a binary frame arrived without an import header",
));
continue;
};
let import = tokio::task::spawn_blocking(move || {
zhila_material::import_bytes(&name, seal, bytes.to_vec())
})
.await;
match import {
Ok(Ok(core)) => {
let summary = core.summary.clone();
let state = {
let mut session = shared.session.lock().await;
session.import_core(core, Actor::Hand)
};
let _ = direct_tx.send(Outbound::Text(
serde_json::to_string(&ServerMessage::Imported { id, core: summary })
.expect("import serializes"),
));
let snapshot = ServerMessage::Snapshot {
revision: state.revision,
state,
};
let _ = shared
.out
.send(Outbound::Text(serde_json::to_string(&snapshot).unwrap()));
}
Ok(Err(error)) => {
let _ = direct_tx.send(error_message(
Some(id),
ErrorCode::BadRequest,
error.to_string(),
));
}
Err(error) => {
warn!("import task failed: {error}");
let _ = direct_tx.send(error_message(
Some(id),
ErrorCode::Internal,
"the import task failed",
));
}
}
continue;
}
Ok(Message::Close(_)) => break,
Ok(_) => continue,
Err(error) => {
@@ -330,6 +381,17 @@ async fn handle(stream: TcpStream, shared: Shared) -> anyhow::Result<()> {
ClientMessage::Hello { .. } => {
let _ = direct_tx.send(error_message(None, ErrorCode::Protocol, "already greeted"));
}
ClientMessage::ImportCore { id, name, seal } => {
if pending_import.is_some() {
let _ = direct_tx.send(error_message(
Some(id),
ErrorCode::BadRequest,
"the previous import has not received its bytes yet",
));
} else {
pending_import = Some((id, name, seal));
}
}
ClientMessage::Command { id, command } => {
let outcome = {
let mut session = shared.session.lock().await;
@@ -403,9 +465,13 @@ fn origin_allowed(origin: &str) -> bool {
fn error_code(error: &CoreError) -> ErrorCode {
match error {
CoreError::InvalidTempo | CoreError::InvalidVelocity | CoreError::InvalidGain => {
ErrorCode::BadRequest
}
CoreError::InvalidTempo
| CoreError::InvalidVelocity
| CoreError::InvalidGain
| CoreError::InvalidRow
| CoreError::InvalidStep
| CoreError::UnknownFace
| CoreError::UnsupportedCommand => ErrorCode::BadRequest,
}
}