bevy-remote-devtools-plugin 0.2.0

A toolset that allows you to debug / view any bevy application with a tauri based UI. This crate is only the plugin part.
Documentation
use bevy::{
    diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin},
    prelude::DynamicScene,
    reflect::TypeRegistryArc,
    scene::serde::SceneSerializer,
    window::Windows,
};
use rweb::*;
use serde::Serialize;
use std::{convert::Infallible, sync::Mutex};

use crate::{
    assets::{assets, get_asset_mesh},
    render_graph::get_render_graph,
    serialization::NumberToStringSerializer,
    sync::{execute_in_world, ExecutionChannel},
    tracing_tracking::{get_tracing_events, trace_frames},
    DevToolsSettings,
};

const VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Serialize, Schema, Debug)]
struct Info {
    name: String,
    version: String,
}

#[get("/v1/info")]
#[cors(origins("*"))]
async fn info() -> Result<Json<Info>, Infallible> {
    let name = execute_in_world(ExecutionChannel::FrameStart, |world| {
        let name = world
            .get_resource::<DevToolsSettings>()
            .map(|settings| settings.name.clone())
            .flatten();
        let window_title = world
            .get_resource::<Windows>()
            .map(|windows| {
                windows
                    .get_primary()
                    .map(|primary| primary.title().to_string())
            })
            .flatten();
        name.or(window_title).unwrap_or_else(|| "Bevy".to_string())
    })
    .await;

    Ok(Info {
        name,
        version: VERSION.to_string(),
    }
    .into())
}
//        diagnostics: Res<Diagnostics>,

#[derive(Serialize, Schema, Debug, Default)]
struct FrameDiagnostics {
    fps: Option<f64>,
    frame_time: Option<f64>,
}

#[get("/v1/diagnostics/frame")]
#[cors(origins("*"))]
async fn diagnostics_frame() -> Result<Json<FrameDiagnostics>, Infallible> {
    let output = execute_in_world(ExecutionChannel::FrameEnd, |world| {
        if let Some(diagnostics) = world.get_resource::<Diagnostics>() {
            let fps = diagnostics
                .get(FrameTimeDiagnosticsPlugin::FPS)
                .map(|fps| fps.value())
                .flatten();
            let frame_time = diagnostics
                .get(FrameTimeDiagnosticsPlugin::FRAME_TIME)
                .map(|time| time.value())
                .flatten();
            Some(FrameDiagnostics { fps, frame_time })
        } else {
            None
        }
    })
    .await;
    Ok(output.unwrap_or_default().into())
}

#[get("/v1/world")]
#[cors(origins("*"))]
async fn world() -> Result<String, Infallible> {
    let json = execute_in_world(ExecutionChannel::FrameEnd, |world| {
        let type_registry = world.get_resource::<TypeRegistryArc>().unwrap();
        let scene = DynamicScene::from_world(world, type_registry);
        let serializer = SceneSerializer::new(&scene, type_registry);
        serde_json::to_string(&NumberToStringSerializer(serializer)).unwrap()
    })
    .await;

    Ok(json)
}

async fn api_main(port: u16) {
    let (spec, filter) = openapi::spec().build(move || {
        get_tracing_events()
            .boxed()
            .or(get_render_graph().boxed())
            .or(info().boxed())
            .or(world().boxed())
            .or(assets().boxed())
            .or(get_asset_mesh().boxed())
            .or(trace_frames().boxed())
            .or(diagnostics_frame().boxed())
            .boxed()
    });

    let cors = warp::cors()
        .allow_any_origin()
        .allow_headers(vec![
            "User-Agent",
            "Sec-Fetch-Mode",
            "Referer",
            "Origin",
            "Access-Control-Request-Method",
            "Access-Control-Request-Headers",
            "Content-Type",
        ])
        .allow_methods(vec!["POST", "GET"])
        .build();

    serve(filter.or(openapi_docs(spec)).with(cors))
        .run(([0, 0, 0, 0], port))
        .await;
}

pub(crate) fn start(port: u16) {
    // Run mdns responder to advertise itself on the network.
    let _ = std::thread::spawn(move || {
        let responder = libmdns::Responder::new().unwrap();
        let _svc = responder.register(
            "_http._tcp".to_owned(),
            "bevy-remote-v1".to_owned(),
            port,
            &["path=/"],
        );

        loop {
            ::std::thread::sleep(::std::time::Duration::from_secs(10));
        }
    });

    let _ = std::thread::spawn(move || {
        let rt = tokio::runtime::Runtime::new().unwrap();
        rt.block_on(async {
            tokio::spawn(api_main(port));
        });
        if let Ok(receiver) = RUNTIME_CHANNEL_SENDER.1.lock() {
            loop {
                let task = receiver.recv().unwrap();
                task(&rt);
            }
        }
    });
}

lazy_static::lazy_static! {
  static ref RUNTIME_CHANNEL_SENDER: (Mutex<std::sync::mpsc::Sender<ChannelType>>, Mutex<std::sync::mpsc::Receiver<ChannelType>>) = {
    let (rx, tx) = std::sync::mpsc::channel();
    (Mutex::new(rx), Mutex::new(tx))
  };
}

type ChannelType = Box<dyn FnOnce(&tokio::runtime::Runtime) + Send>;