blob: d92cd4919b46cf3c9e93359f13b5de0ba4b640d3 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{Context as _, Error};
use fidl::endpoints::RequestStream as _;
use fidl_examples_canvas_baseline::{BoundingBox, InstanceRequest, InstanceRequestStream, Point};
use fuchsia_async::{MonotonicInstant, Timer};
use fuchsia_component::server::ServiceFs;
use futures::future::join;
use futures::prelude::*;
use std::sync::{Arc, Mutex};
// A struct that stores the two things we care about for this example: the bounding box the lines
// that have been added thus far, and bit to track whether or not there have been changes since the
// last `OnDrawn` event.
#[derive(Debug)]
struct CanvasState {
// Tracks whether there has been a change since the last send, to prevent redundant updates.
changed: bool,
bounding_box: BoundingBox,
}
/// Handler for the `AddLine` method.
fn add_line(state: &mut CanvasState, line: [Point; 2]) {
// Update the bounding box to account for the new lines we've just "added" to the canvas.
let bounds = &mut state.bounding_box;
for point in line {
if point.x < bounds.top_left.x {
bounds.top_left.x = point.x;
}
if point.y > bounds.top_left.y {
bounds.top_left.y = point.y;
}
if point.x > bounds.bottom_right.x {
bounds.bottom_right.x = point.x;
}
if point.y < bounds.bottom_right.y {
bounds.bottom_right.y = point.y;
}
}
// Mark the state as "dirty", so that an update is sent back to the client on the next tick.
state.changed = true
}
/// Creates a new instance of the server, paired to a single client across a zircon channel.
async fn run_server(stream: InstanceRequestStream) -> Result<(), Error> {
// Create a new in-memory state store for the state of the canvas. The store will live for the
// lifetime of the connection between the server and this particular client.
let state = Arc::new(Mutex::new(CanvasState {
changed: true,
bounding_box: BoundingBox {
top_left: Point { x: 0, y: 0 },
bottom_right: Point { x: 0, y: 0 },
},
}));
// Take ownership of the control_handle from the stream, which will allow us to push events from
// a different async task.
let control_handle = stream.control_handle();
// A separate watcher task periodically "draws" the canvas, and notifies the client of the new
// state. We'll need a cloned reference to the canvas state to be accessible from the new
// task.
let state_ref = state.clone();
let update_sender = || async move {
loop {
// Our server sends one update per second.
Timer::new(MonotonicInstant::after(zx::MonotonicDuration::from_seconds(1))).await;
let mut state = state_ref.lock().unwrap();
if !state.changed {
continue;
}
// After acquiring the lock, this is where we would draw the actual lines. Since this is
// just an example, we'll avoid doing the actual rendering, and simply send the bounding
// box to the client instead.
let bounds = state.bounding_box;
match control_handle.send_on_drawn(&bounds.top_left, &bounds.bottom_right) {
Ok(_) => println!(
"OnDrawn event sent: top_left: {:?}, bottom_right: {:?}",
bounds.top_left, bounds.bottom_right
),
Err(_) => return,
}
// Reset the change tracker.
state.changed = false
}
};
// Handle requests on the protocol sequentially - a new request is not handled until its
// predecessor has been processed.
let state_ref = &state;
let request_handler =
stream.map(|result| result.context("failed request")).try_for_each(|request| async move {
// Match based on the method being invoked.
match request {
InstanceRequest::AddLine { line, .. } => {
println!("AddLine request received: {:?}", line);
add_line(&mut state_ref.lock().unwrap(), line);
}
InstanceRequest::_UnknownMethod { ordinal, .. } => {
println!("Received an unknown method with ordinal {ordinal}");
}
}
Ok(())
});
// This line will only be reached if the server errors out. The stream will await indefinitely,
// thereby creating a long-lived server. Here, we first wait for the updater task to realize the
// connection has died, then bubble up the error.
join(request_handler, update_sender()).await.0
}
// A helper enum that allows us to treat a `Instance` service instance as a value.
enum IncomingService {
Instance(InstanceRequestStream),
}
#[fuchsia::main]
async fn main() -> Result<(), Error> {
println!("Started");
// Add a discoverable instance of our `Instance` protocol - this will allow the client to see
// the server and connect to it.
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingService::Instance);
fs.take_and_serve_directory_handle()?;
println!("Listening for incoming connections");
// The maximum number of concurrent clients that may be served by this process.
const MAX_CONCURRENT: usize = 10;
// Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Instance(stream)| {
run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
})
.await;
Ok(())
}