Skip to content

Instantly share code, notes, and snippets.

@Xion
Last active October 18, 2022 18:00
Show Gist options
  • Save Xion/98763ce5daa21c83740f1218f5b88fba to your computer and use it in GitHub Desktop.
Save Xion/98763ce5daa21c83740f1218f5b88fba to your computer and use it in GitHub Desktop.
Count installed Bevy plugins
// License: BSD-3
use std::io::{self, Write};
use std::string::FromUtf8Error;
use std::sync::Arc;
use antidote::RwLock;
use bevy::prelude::*;
use bevy::reflect::FromReflect;
use bevy::utils::tracing::{self, Dispatch};
use regex::Regex;
use tracing_subscriber::fmt::MakeWriter;
/// Resource storing the results of [`trace_app_bootstrap`].
#[derive(Debug, Default, FromReflect, Reflect)]
#[reflect(Default)]
pub struct AppBootstrapTrace {
/// Names of installed Bevy [`Plugin`]s.
pub plugin_names: Vec<String>,
}
/// Trace the bootstrap of a Bevy [`App`].
///
/// Gathered information is stored as [`AppBootstrapTrace`] resource.
pub fn trace_app_bootstrap(f: impl FnOnce() -> App) -> App {
let tracer = BootstrapTracer::default();
let subscriber = tracing_subscriber::fmt()
.with_env_filter("off,bevy_app=debug")
.with_level(false)
.with_line_number(false)
.with_writer(tracer.clone())
.finish();
let dispatcher = Dispatch::new(subscriber);
let mut app = tracing::dispatcher::with_default(&dispatcher, f);
let output: String = tracer.try_into().unwrap();
let mut trace = AppBootstrapTrace::default();
for plugin in ADDED_PLUGIN_LOG_RE.captures_iter(&output) {
trace.plugin_names.push(plugin["name"].to_string());
}
// If we haven't captured anything, then it's almost certainly be Bevy changing the debug!()
// log in [`App::add_plugin`] (as it's obviously not part of the API).
if trace.plugin_names.is_empty() {
warn!("No Bevy plugin names captured! Check for changes in bevy_app::App::add_plugin()");
} else {
app.insert_resource(trace);
}
app
}
lazy_static! {
/// Regex extracting logs emitted by [`bevy_app::App::add_plugin`].
static ref ADDED_PLUGIN_LOG_RE: Regex = Regex::new("added plugin: (?P<name>.+)\n").unwrap();
}
/// Gathers [`tracing`] output and allows to retrieve it as a string.
#[derive(Clone, Debug)]
struct BootstrapTracer {
output: Arc<RwLock<Vec<SyncVecWriter>>>,
}
impl Default for BootstrapTracer {
fn default() -> Self {
Self { output: Arc::new(RwLock::new(Vec::new())) }
}
}
impl<'w> MakeWriter<'w> for BootstrapTracer {
type Writer = SyncVecWriter;
fn make_writer(&'w self) -> Self::Writer {
let mut output = self.output.write();
output.push(SyncVecWriter::default());
output.last().unwrap().clone()
}
}
impl TryFrom<BootstrapTracer> for String {
type Error = FromUtf8Error;
fn try_from(tracer: BootstrapTracer) -> Result<String, Self::Error> {
let mut string = String::new();
for chunk in tracer.output.read().iter() {
string += &String::try_from(chunk.clone())?;
}
Ok(string)
}
}
/// A thread-safe writer to an in-memory [`Vec`] of bytes.
#[derive(Clone, Debug)]
struct SyncVecWriter {
buf: Arc<RwLock<Vec<u8>>>,
}
impl Default for SyncVecWriter {
fn default() -> Self {
Self { buf: Arc::new(RwLock::new(Vec::new())) }
}
}
impl Write for SyncVecWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.write().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.buf.write().flush()
}
}
impl TryFrom<SyncVecWriter> for String {
type Error = FromUtf8Error;
fn try_from(writer: SyncVecWriter) -> Result<String, Self::Error> {
String::from_utf8(writer.buf.read().clone())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment