Use type_name as plugin name default
This commit is contained in:
parent
7bb889bada
commit
0202dcb009
@ -368,7 +368,7 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(RegisterAppPlugin)]
|
#[proc_macro_derive(DynamicAppPlugin)]
|
||||||
pub fn derive_app_plugin(input: TokenStream) -> TokenStream {
|
pub fn derive_app_plugin(input: TokenStream) -> TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
let struct_name = &ast.ident;
|
let struct_name = &ast.ident;
|
||||||
|
|||||||
@ -1,48 +1,34 @@
|
|||||||
use bevy::{prelude::*, plugin::AppPlugin};
|
use bevy::prelude::*;
|
||||||
|
|
||||||
#[derive(RegisterAppPlugin)]
|
#[derive(DynamicAppPlugin)]
|
||||||
pub struct ExamplePlugin;
|
pub struct ExamplePlugin;
|
||||||
|
|
||||||
impl AppPlugin for ExamplePlugin {
|
impl AppPlugin for ExamplePlugin {
|
||||||
fn build(&self, app_builder: AppBuilder) -> AppBuilder {
|
fn build(&self, app_builder: AppBuilder) -> AppBuilder {
|
||||||
app_builder.setup(setup)
|
app_builder.setup(setup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"example"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(world: &mut World, resources: &mut Resources) {
|
pub fn setup(world: &mut World, resources: &mut Resources) {
|
||||||
let mut mesh_storage = resources.get_mut::<AssetStorage<Mesh>>().unwrap();
|
let mut mesh_storage = resources.get_mut::<AssetStorage<Mesh>>().unwrap();
|
||||||
|
let mut material_storage = resources
|
||||||
|
.get_mut::<AssetStorage<StandardMaterial>>()
|
||||||
|
.unwrap();
|
||||||
let cube_handle = mesh_storage.add(Mesh::load(MeshType::Cube));
|
let cube_handle = mesh_storage.add(Mesh::load(MeshType::Cube));
|
||||||
let plane_handle = mesh_storage.add(Mesh::load(MeshType::Plane { size: 10.0 }));
|
let cube_material_handle = material_storage.add(StandardMaterial {
|
||||||
|
albedo: Color::rgb(0.5, 0.4, 0.3),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
world.build()
|
world.build()
|
||||||
// plane
|
|
||||||
.add_entity(MeshEntity {
|
|
||||||
mesh: plane_handle,
|
|
||||||
material: StandardMaterial {
|
|
||||||
albedo: Color::rgb(0.1, 0.2, 0.1),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
// cube
|
// cube
|
||||||
.add_entity(MeshEntity {
|
.add_entity(MeshEntity {
|
||||||
mesh: cube_handle,
|
mesh: cube_handle,
|
||||||
material: StandardMaterial {
|
material: cube_material_handle,
|
||||||
albedo: Color::rgb(0.5, 0.4, 0.3),
|
|
||||||
},
|
|
||||||
translation: Translation::new(0.0, 0.0, 1.0),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
// light
|
// light
|
||||||
.add_entity(LightEntity {
|
.add_entity(LightEntity {
|
||||||
light: Light {
|
|
||||||
color: Color::rgb(0.8, 0.8, 0.5),
|
|
||||||
fov: f32::to_radians(60.0),
|
|
||||||
depth: 0.1..50.0,
|
|
||||||
},
|
|
||||||
translation: Translation::new(4.0, -4.0, 5.0),
|
translation: Translation::new(4.0, -4.0, 5.0),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
@ -5,7 +5,7 @@ fn main() {
|
|||||||
.add_default_plugins()
|
.add_default_plugins()
|
||||||
.load_plugin(concat!(
|
.load_plugin(concat!(
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
"/examples/plugin_loading/example_plugin/target/release/libexample_plugin.so"
|
"/examples/dynamic_plugin_loading/example_plugin/target/release/libexample_plugin.so"
|
||||||
))
|
))
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
@ -27,6 +27,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build() -> AppBuilder {
|
pub fn build() -> AppBuilder {
|
||||||
|
env_logger::init();
|
||||||
AppBuilder::new()
|
AppBuilder::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -226,6 +226,7 @@ impl AppBuilder {
|
|||||||
|
|
||||||
pub fn load_plugin(self, path: &str) -> Self {
|
pub fn load_plugin(self, path: &str) -> Self {
|
||||||
let (_lib, plugin) = load_plugin(path);
|
let (_lib, plugin) = load_plugin(path);
|
||||||
|
log::debug!("loaded plugin: {}", plugin.name());
|
||||||
plugin.build(self)
|
plugin.build(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +234,7 @@ impl AppBuilder {
|
|||||||
where
|
where
|
||||||
T: AppPlugin,
|
T: AppPlugin,
|
||||||
{
|
{
|
||||||
|
log::debug!("added plugin: {}", plugin.name());
|
||||||
plugin.build(self)
|
plugin.build(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,9 @@ use std::any::Any;
|
|||||||
|
|
||||||
pub trait AppPlugin: Any + Send + Sync {
|
pub trait AppPlugin: Any + Send + Sync {
|
||||||
fn build(&self, app: AppBuilder) -> AppBuilder;
|
fn build(&self, app: AppBuilder) -> AppBuilder;
|
||||||
// TODO: consider removing "name" in favor of calling type_name::<Plugin>() from the host app
|
fn name(&self) -> &str {
|
||||||
fn name(&self) -> &str;
|
type_name_of_val(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type CreateAppPlugin = unsafe fn() -> *mut dyn AppPlugin;
|
pub type CreateAppPlugin = unsafe fn() -> *mut dyn AppPlugin;
|
||||||
@ -19,3 +20,7 @@ pub fn load_plugin(path: &str) -> (Library, Box<dyn AppPlugin>) {
|
|||||||
(lib, plugin)
|
(lib, plugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
|
||||||
|
std::any::type_name::<T>()
|
||||||
|
}
|
||||||
@ -36,7 +36,4 @@ impl AppPlugin for ScheduleRunnerPlugin {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn name(&self) -> &str {
|
|
||||||
"ScheduleRun"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
use super::Time;
|
|
||||||
use crate::app::{plugin::AppPlugin, AppBuilder};
|
|
||||||
use bevy_transform::transform_system_bundle;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct CorePlugin;
|
|
||||||
|
|
||||||
impl AppPlugin for CorePlugin {
|
|
||||||
fn build(&self, mut app: AppBuilder) -> AppBuilder {
|
|
||||||
for transform_system in transform_system_bundle::build(&mut app.world).drain(..) {
|
|
||||||
app = app.add_system(transform_system);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.add_resource(Time::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Core"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,23 @@
|
|||||||
pub mod bytes;
|
pub mod bytes;
|
||||||
mod core_plugin;
|
|
||||||
pub mod event;
|
pub mod event;
|
||||||
mod time;
|
mod time;
|
||||||
|
|
||||||
pub use bytes::*;
|
pub use bytes::*;
|
||||||
pub use core_plugin::*;
|
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
pub use time::*;
|
pub use time::*;
|
||||||
|
|
||||||
|
use crate::app::{plugin::AppPlugin, AppBuilder};
|
||||||
|
use bevy_transform::transform_system_bundle;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CorePlugin;
|
||||||
|
|
||||||
|
impl AppPlugin for CorePlugin {
|
||||||
|
fn build(&self, mut app: AppBuilder) -> AppBuilder {
|
||||||
|
for transform_system in transform_system_bundle::build(&mut app.world).drain(..) {
|
||||||
|
app = app.add_system(transform_system);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.add_resource(Time::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/diagnostic/diagnostic.rs
Normal file
121
src/diagnostic/diagnostic.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
time::{Duration, SystemTime},
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
|
||||||
|
pub struct DiagnosticId(pub Uuid);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DiagnosticMeasurement {
|
||||||
|
pub time: SystemTime,
|
||||||
|
pub value: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Diagnostic {
|
||||||
|
pub id: DiagnosticId,
|
||||||
|
pub name: String,
|
||||||
|
history: VecDeque<DiagnosticMeasurement>,
|
||||||
|
sum: f64,
|
||||||
|
max_history_length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic {
|
||||||
|
pub fn add_measurement(&mut self, value: f64) {
|
||||||
|
let time = SystemTime::now();
|
||||||
|
if self.history.len() == self.max_history_length {
|
||||||
|
if let Some(removed_diagnostic) = self.history.pop_back() {
|
||||||
|
self.sum -= removed_diagnostic.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sum += value;
|
||||||
|
self.history
|
||||||
|
.push_front(DiagnosticMeasurement { time, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(id: DiagnosticId, name: &str, max_history_length: usize) -> Diagnostic {
|
||||||
|
Diagnostic {
|
||||||
|
id,
|
||||||
|
name: name.to_string(),
|
||||||
|
history: VecDeque::with_capacity(max_history_length),
|
||||||
|
max_history_length,
|
||||||
|
sum: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> Option<f64> {
|
||||||
|
self.history.back().map(|measurement| measurement.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sum(&self) -> f64 {
|
||||||
|
self.sum
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn average(&self) -> Option<f64> {
|
||||||
|
if self.history.len() > 0 {
|
||||||
|
Some(self.sum / self.history.len() as f64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn history_len(&self) -> usize {
|
||||||
|
self.history.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn duration(&self) -> Option<Duration> {
|
||||||
|
if self.history.len() < 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(oldest) = self.history.back() {
|
||||||
|
if let Some(newest) = self.history.front() {
|
||||||
|
return newest.time.duration_since(oldest.time).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_max_history_length(&self) -> usize {
|
||||||
|
self.max_history_length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Diagnostics {
|
||||||
|
diagnostics: HashMap<DiagnosticId, Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostics {
|
||||||
|
pub fn add(&mut self, diagnostic: Diagnostic) {
|
||||||
|
self.diagnostics.insert(diagnostic.id, diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: DiagnosticId) -> Option<&Diagnostic> {
|
||||||
|
self.diagnostics.get(&id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, id: DiagnosticId) -> Option<&mut Diagnostic> {
|
||||||
|
self.diagnostics.get_mut(&id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_measurement(&self, id: DiagnosticId) -> Option<&DiagnosticMeasurement> {
|
||||||
|
self.diagnostics
|
||||||
|
.get(&id)
|
||||||
|
.and_then(|diagnostic| diagnostic.history.front())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_measurement(&mut self, id: DiagnosticId, value: f64) {
|
||||||
|
if let Some(diagnostic) = self.diagnostics.get_mut(&id) {
|
||||||
|
diagnostic.add_measurement(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &Diagnostic> {
|
||||||
|
self.diagnostics.values()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,42 +0,0 @@
|
|||||||
use super::{
|
|
||||||
diagnostics::{frame_time_diagnostic_system, print_diagnostics_system},
|
|
||||||
Diagnostics,
|
|
||||||
};
|
|
||||||
use crate::app::{plugin::AppPlugin, AppBuilder};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
pub struct DiagnosticsPlugin {
|
|
||||||
pub print_wait_duration: Duration,
|
|
||||||
pub print_diagnostics: bool,
|
|
||||||
pub add_defaults: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DiagnosticsPlugin {
|
|
||||||
fn default() -> Self {
|
|
||||||
DiagnosticsPlugin {
|
|
||||||
print_wait_duration: Duration::from_secs_f64(1.0),
|
|
||||||
print_diagnostics: false,
|
|
||||||
add_defaults: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppPlugin for DiagnosticsPlugin {
|
|
||||||
fn build(&self, mut app: AppBuilder) -> AppBuilder {
|
|
||||||
app = app.add_resource(Diagnostics::default());
|
|
||||||
if self.add_defaults {
|
|
||||||
let frame_time_diagnostic_system =
|
|
||||||
{ frame_time_diagnostic_system(&mut app.resources, 10) };
|
|
||||||
app = app.add_system(frame_time_diagnostic_system)
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.print_diagnostics {
|
|
||||||
app = app.add_system(print_diagnostics_system(self.print_wait_duration));
|
|
||||||
}
|
|
||||||
|
|
||||||
app
|
|
||||||
}
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Diagnostics"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,125 +1,40 @@
|
|||||||
mod diagnostic_plugin;
|
mod diagnostic;
|
||||||
pub mod diagnostics;
|
pub mod diagnostics;
|
||||||
pub use diagnostic_plugin::*;
|
pub use diagnostic::*;
|
||||||
|
|
||||||
use std::{
|
use crate::app::{plugin::AppPlugin, AppBuilder};
|
||||||
collections::{HashMap, VecDeque},
|
use std::time::Duration;
|
||||||
time::{Duration, SystemTime},
|
use diagnostics::{print_diagnostics_system, frame_time_diagnostic_system};
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
|
pub struct DiagnosticsPlugin {
|
||||||
pub struct DiagnosticId(Uuid);
|
pub print_wait_duration: Duration,
|
||||||
|
pub print_diagnostics: bool,
|
||||||
#[derive(Debug)]
|
pub add_defaults: bool,
|
||||||
pub struct DiagnosticMeasurement {
|
|
||||||
pub time: SystemTime,
|
|
||||||
pub value: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl Default for DiagnosticsPlugin {
|
||||||
pub struct Diagnostic {
|
fn default() -> Self {
|
||||||
pub id: DiagnosticId,
|
DiagnosticsPlugin {
|
||||||
pub name: String,
|
print_wait_duration: Duration::from_secs_f64(1.0),
|
||||||
history: VecDeque<DiagnosticMeasurement>,
|
print_diagnostics: false,
|
||||||
sum: f64,
|
add_defaults: true,
|
||||||
max_history_length: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Diagnostic {
|
|
||||||
pub fn add_measurement(&mut self, value: f64) {
|
|
||||||
let time = SystemTime::now();
|
|
||||||
if self.history.len() == self.max_history_length {
|
|
||||||
if let Some(removed_diagnostic) = self.history.pop_back() {
|
|
||||||
self.sum -= removed_diagnostic.value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sum += value;
|
|
||||||
self.history
|
|
||||||
.push_front(DiagnosticMeasurement { time, value });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(id: DiagnosticId, name: &str, max_history_length: usize) -> Diagnostic {
|
|
||||||
Diagnostic {
|
|
||||||
id,
|
|
||||||
name: name.to_string(),
|
|
||||||
history: VecDeque::with_capacity(max_history_length),
|
|
||||||
max_history_length,
|
|
||||||
sum: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value(&self) -> Option<f64> {
|
|
||||||
self.history.back().map(|measurement| measurement.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sum(&self) -> f64 {
|
|
||||||
self.sum
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn average(&self) -> Option<f64> {
|
|
||||||
if self.history.len() > 0 {
|
|
||||||
Some(self.sum / self.history.len() as f64)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn history_len(&self) -> usize {
|
|
||||||
self.history.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn duration(&self) -> Option<Duration> {
|
|
||||||
if self.history.len() < 2 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(oldest) = self.history.back() {
|
|
||||||
if let Some(newest) = self.history.front() {
|
|
||||||
return newest.time.duration_since(oldest.time).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_max_history_length(&self) -> usize {
|
|
||||||
self.max_history_length
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl AppPlugin for DiagnosticsPlugin {
|
||||||
pub struct Diagnostics {
|
fn build(&self, mut app: AppBuilder) -> AppBuilder {
|
||||||
diagnostics: HashMap<DiagnosticId, Diagnostic>,
|
app = app.add_resource(Diagnostics::default());
|
||||||
}
|
if self.add_defaults {
|
||||||
|
let frame_time_diagnostic_system =
|
||||||
impl Diagnostics {
|
{ frame_time_diagnostic_system(&mut app.resources, 10) };
|
||||||
pub fn add(&mut self, diagnostic: Diagnostic) {
|
app = app.add_system(frame_time_diagnostic_system)
|
||||||
self.diagnostics.insert(diagnostic.id, diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, id: DiagnosticId) -> Option<&Diagnostic> {
|
|
||||||
self.diagnostics.get(&id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: DiagnosticId) -> Option<&mut Diagnostic> {
|
|
||||||
self.diagnostics.get_mut(&id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_measurement(&self, id: DiagnosticId) -> Option<&DiagnosticMeasurement> {
|
|
||||||
self.diagnostics
|
|
||||||
.get(&id)
|
|
||||||
.and_then(|diagnostic| diagnostic.history.front())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_measurement(&mut self, id: DiagnosticId, value: f64) {
|
|
||||||
if let Some(diagnostic) = self.diagnostics.get_mut(&id) {
|
|
||||||
diagnostic.add_measurement(value);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &Diagnostic> {
|
if self.print_diagnostics {
|
||||||
self.diagnostics.values()
|
app = app.add_system(print_diagnostics_system(self.print_wait_duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,8 +14,4 @@ impl AppPlugin for InputPlugin {
|
|||||||
.add_event::<MouseButtonInput>()
|
.add_event::<MouseButtonInput>()
|
||||||
.add_event::<MouseMotion>()
|
.add_event::<MouseMotion>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Input"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,14 +21,14 @@ impl DrawTarget for AssignedBatchesDrawTarget {
|
|||||||
render_pass: &mut dyn RenderPass,
|
render_pass: &mut dyn RenderPass,
|
||||||
pipeline_handle: Handle<PipelineDescriptor>,
|
pipeline_handle: Handle<PipelineDescriptor>,
|
||||||
) {
|
) {
|
||||||
log::debug!("drawing batches for pipeline {:?}", pipeline_handle);
|
log::trace!("drawing batches for pipeline {:?}", pipeline_handle);
|
||||||
let asset_batches = resources.get::<AssetBatchers>().unwrap();
|
let asset_batches = resources.get::<AssetBatchers>().unwrap();
|
||||||
let global_render_resource_assignments =
|
let global_render_resource_assignments =
|
||||||
resources.get::<RenderResourceAssignments>().unwrap();
|
resources.get::<RenderResourceAssignments>().unwrap();
|
||||||
render_pass.set_render_resources(&global_render_resource_assignments);
|
render_pass.set_render_resources(&global_render_resource_assignments);
|
||||||
for batch in asset_batches.get_batches() {
|
for batch in asset_batches.get_batches() {
|
||||||
let indices = render_pass.set_render_resources(&batch.render_resource_assignments);
|
let indices = render_pass.set_render_resources(&batch.render_resource_assignments);
|
||||||
log::debug!("drawing batch {:?}", batch.render_resource_assignments.id);
|
log::trace!("drawing batch {:?}", batch.render_resource_assignments.id);
|
||||||
log::trace!("{:#?}", batch);
|
log::trace!("{:#?}", batch);
|
||||||
for batched_entity in batch.entities.iter() {
|
for batched_entity in batch.entities.iter() {
|
||||||
let renderable = world.get_component::<Renderable>(*batched_entity).unwrap();
|
let renderable = world.get_component::<Renderable>(*batched_entity).unwrap();
|
||||||
@ -75,7 +75,7 @@ impl DrawTarget for AssignedBatchesDrawTarget {
|
|||||||
let mut global_render_resource_assignments =
|
let mut global_render_resource_assignments =
|
||||||
resources.get_mut::<RenderResourceAssignments>().unwrap();
|
resources.get_mut::<RenderResourceAssignments>().unwrap();
|
||||||
|
|
||||||
log::debug!(
|
log::trace!(
|
||||||
"setting up batch bind groups for pipeline: {:?} {:?}",
|
"setting up batch bind groups for pipeline: {:?} {:?}",
|
||||||
pipeline_handle,
|
pipeline_handle,
|
||||||
pipeline_descriptor.name,
|
pipeline_descriptor.name,
|
||||||
@ -84,7 +84,7 @@ impl DrawTarget for AssignedBatchesDrawTarget {
|
|||||||
renderer.setup_bind_groups(&mut global_render_resource_assignments, pipeline_descriptor);
|
renderer.setup_bind_groups(&mut global_render_resource_assignments, pipeline_descriptor);
|
||||||
|
|
||||||
for batch in asset_batches.get_batches_mut() {
|
for batch in asset_batches.get_batches_mut() {
|
||||||
log::debug!(
|
log::trace!(
|
||||||
"setting up batch bind groups: {:?}",
|
"setting up batch bind groups: {:?}",
|
||||||
batch.render_resource_assignments.id
|
batch.render_resource_assignments.id
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
mod camera;
|
mod camera;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
pub mod render_graph;
|
pub mod render_graph;
|
||||||
mod render_plugin;
|
|
||||||
pub mod shader;
|
pub mod shader;
|
||||||
|
|
||||||
mod color;
|
mod color;
|
||||||
@ -11,7 +10,6 @@ mod vertex;
|
|||||||
pub use camera::*;
|
pub use camera::*;
|
||||||
pub use color::*;
|
pub use color::*;
|
||||||
pub use light::*;
|
pub use light::*;
|
||||||
pub use render_plugin::*;
|
|
||||||
pub use renderable::*;
|
pub use renderable::*;
|
||||||
|
|
||||||
pub use vertex::Vertex;
|
pub use vertex::Vertex;
|
||||||
@ -23,3 +21,81 @@ pub mod render_resource;
|
|||||||
mod renderable;
|
mod renderable;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
|
|
||||||
|
use self::{
|
||||||
|
draw_target::draw_targets::{
|
||||||
|
AssignedBatchesDrawTarget, AssignedMeshesDrawTarget, MeshesDrawTarget, UiDrawTarget,
|
||||||
|
},
|
||||||
|
pass::passes::ForwardPassBuilder,
|
||||||
|
pipeline::{
|
||||||
|
pipelines::ForwardPipelineBuilder, PipelineCompiler, ShaderPipelineAssignments,
|
||||||
|
VertexBufferDescriptors,
|
||||||
|
},
|
||||||
|
render_graph::RenderGraph,
|
||||||
|
render_resource::{
|
||||||
|
build_entity_render_resource_assignments_system,
|
||||||
|
resource_providers::{
|
||||||
|
Camera2dResourceProvider, CameraResourceProvider, LightResourceProvider,
|
||||||
|
MeshResourceProvider, UiResourceProvider,
|
||||||
|
},
|
||||||
|
AssetBatchers, EntityRenderResourceAssignments, RenderResourceAssignments,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{prelude::*, window::WindowResized};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RenderPlugin;
|
||||||
|
|
||||||
|
impl RenderPlugin {
|
||||||
|
pub fn setup_render_graph_defaults(app: &mut AppBuilder) {
|
||||||
|
let mut pipelines = app
|
||||||
|
.resources
|
||||||
|
.get_mut::<AssetStorage<PipelineDescriptor>>()
|
||||||
|
.unwrap();
|
||||||
|
let mut shaders = app.resources.get_mut::<AssetStorage<Shader>>().unwrap();
|
||||||
|
let mut render_graph = app.resources.get_mut::<RenderGraph>().unwrap();
|
||||||
|
render_graph
|
||||||
|
.build(&mut pipelines, &mut shaders)
|
||||||
|
.add_draw_target(MeshesDrawTarget::default())
|
||||||
|
.add_draw_target(AssignedBatchesDrawTarget::default())
|
||||||
|
.add_draw_target(AssignedMeshesDrawTarget::default())
|
||||||
|
.add_draw_target(UiDrawTarget::default())
|
||||||
|
.add_resource_provider(CameraResourceProvider::new(
|
||||||
|
app.resources.get_event_reader::<WindowResized>(),
|
||||||
|
))
|
||||||
|
.add_resource_provider(Camera2dResourceProvider::new(
|
||||||
|
app.resources.get_event_reader::<WindowResized>(),
|
||||||
|
))
|
||||||
|
.add_resource_provider(LightResourceProvider::new(10))
|
||||||
|
.add_resource_provider(UiResourceProvider::new())
|
||||||
|
.add_resource_provider(MeshResourceProvider::new())
|
||||||
|
.add_resource_provider(UniformResourceProvider::<StandardMaterial>::new(true))
|
||||||
|
.add_resource_provider(UniformResourceProvider::<LocalToWorld>::new(true))
|
||||||
|
.add_forward_pass()
|
||||||
|
.add_forward_pipeline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppPlugin for RenderPlugin {
|
||||||
|
fn build(&self, mut app: AppBuilder) -> AppBuilder {
|
||||||
|
let mut asset_batchers = AssetBatchers::default();
|
||||||
|
asset_batchers.batch_types2::<Mesh, StandardMaterial>();
|
||||||
|
app = app
|
||||||
|
.add_system(build_entity_render_resource_assignments_system())
|
||||||
|
.add_resource(RenderGraph::default())
|
||||||
|
.add_resource(AssetStorage::<Mesh>::new())
|
||||||
|
.add_resource(AssetStorage::<Texture>::new())
|
||||||
|
.add_resource(AssetStorage::<Shader>::new())
|
||||||
|
.add_resource(AssetStorage::<StandardMaterial>::new())
|
||||||
|
.add_resource(AssetStorage::<PipelineDescriptor>::new())
|
||||||
|
.add_resource(ShaderPipelineAssignments::new())
|
||||||
|
.add_resource(VertexBufferDescriptors::default())
|
||||||
|
.add_resource(PipelineCompiler::new())
|
||||||
|
.add_resource(RenderResourceAssignments::default())
|
||||||
|
.add_resource(EntityRenderResourceAssignments::default())
|
||||||
|
.add_resource(asset_batchers);
|
||||||
|
RenderPlugin::setup_render_graph_defaults(&mut app);
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,77 +0,0 @@
|
|||||||
use super::{
|
|
||||||
draw_target::draw_targets::{
|
|
||||||
AssignedBatchesDrawTarget, AssignedMeshesDrawTarget, MeshesDrawTarget, UiDrawTarget,
|
|
||||||
},
|
|
||||||
pass::passes::ForwardPassBuilder,
|
|
||||||
pipeline::{pipelines::ForwardPipelineBuilder, PipelineCompiler, ShaderPipelineAssignments, VertexBufferDescriptors},
|
|
||||||
render_graph::RenderGraph,
|
|
||||||
render_resource::{
|
|
||||||
build_entity_render_resource_assignments_system,
|
|
||||||
resource_providers::{
|
|
||||||
Camera2dResourceProvider, CameraResourceProvider, LightResourceProvider,
|
|
||||||
MeshResourceProvider, UiResourceProvider,
|
|
||||||
},
|
|
||||||
AssetBatchers, EntityRenderResourceAssignments, RenderResourceAssignments,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use crate::{prelude::*, window::WindowResized};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct RenderPlugin;
|
|
||||||
|
|
||||||
impl RenderPlugin {
|
|
||||||
pub fn setup_render_graph_defaults(app: &mut AppBuilder) {
|
|
||||||
let mut pipelines = app
|
|
||||||
.resources
|
|
||||||
.get_mut::<AssetStorage<PipelineDescriptor>>()
|
|
||||||
.unwrap();
|
|
||||||
let mut shaders = app.resources.get_mut::<AssetStorage<Shader>>().unwrap();
|
|
||||||
let mut render_graph = app.resources.get_mut::<RenderGraph>().unwrap();
|
|
||||||
render_graph
|
|
||||||
.build(&mut pipelines, &mut shaders)
|
|
||||||
.add_draw_target(MeshesDrawTarget::default())
|
|
||||||
.add_draw_target(AssignedBatchesDrawTarget::default())
|
|
||||||
.add_draw_target(AssignedMeshesDrawTarget::default())
|
|
||||||
.add_draw_target(UiDrawTarget::default())
|
|
||||||
.add_resource_provider(CameraResourceProvider::new(
|
|
||||||
app.resources.get_event_reader::<WindowResized>(),
|
|
||||||
))
|
|
||||||
.add_resource_provider(Camera2dResourceProvider::new(
|
|
||||||
app.resources.get_event_reader::<WindowResized>(),
|
|
||||||
))
|
|
||||||
.add_resource_provider(LightResourceProvider::new(10))
|
|
||||||
.add_resource_provider(UiResourceProvider::new())
|
|
||||||
.add_resource_provider(MeshResourceProvider::new())
|
|
||||||
.add_resource_provider(UniformResourceProvider::<StandardMaterial>::new(true))
|
|
||||||
.add_resource_provider(UniformResourceProvider::<LocalToWorld>::new(true))
|
|
||||||
.add_forward_pass()
|
|
||||||
.add_forward_pipeline();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppPlugin for RenderPlugin {
|
|
||||||
fn build(&self, mut app: AppBuilder) -> AppBuilder {
|
|
||||||
let mut asset_batchers = AssetBatchers::default();
|
|
||||||
asset_batchers.batch_types2::<Mesh, StandardMaterial>();
|
|
||||||
app = app
|
|
||||||
.add_system(build_entity_render_resource_assignments_system())
|
|
||||||
.add_resource(RenderGraph::default())
|
|
||||||
.add_resource(AssetStorage::<Mesh>::new())
|
|
||||||
.add_resource(AssetStorage::<Texture>::new())
|
|
||||||
.add_resource(AssetStorage::<Shader>::new())
|
|
||||||
.add_resource(AssetStorage::<StandardMaterial>::new())
|
|
||||||
.add_resource(AssetStorage::<PipelineDescriptor>::new())
|
|
||||||
.add_resource(ShaderPipelineAssignments::new())
|
|
||||||
.add_resource(VertexBufferDescriptors::default())
|
|
||||||
.add_resource(PipelineCompiler::new())
|
|
||||||
.add_resource(RenderResourceAssignments::default())
|
|
||||||
.add_resource(EntityRenderResourceAssignments::default())
|
|
||||||
.add_resource(asset_batchers);
|
|
||||||
RenderPlugin::setup_render_graph_defaults(&mut app);
|
|
||||||
app
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Render"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -24,9 +24,6 @@ impl AppPlugin for WgpuRendererPlugin {
|
|||||||
let render_system = wgpu_render_system(&app.resources);
|
let render_system = wgpu_render_system(&app.resources);
|
||||||
app.add_thread_local_to_stage(system_stage::RENDER, render_system)
|
app.add_thread_local_to_stage(system_stage::RENDER, render_system)
|
||||||
}
|
}
|
||||||
fn name(&self) -> &str {
|
|
||||||
"WgpuRenderer"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wgpu_render_system(resources: &Resources) -> impl FnMut(&mut World, &mut Resources) {
|
pub fn wgpu_render_system(resources: &Resources) -> impl FnMut(&mut World, &mut Resources) {
|
||||||
|
|||||||
@ -60,7 +60,7 @@ impl WgpuResources {
|
|||||||
if let Some((render_resource_set_id, _indices)) =
|
if let Some((render_resource_set_id, _indices)) =
|
||||||
render_resource_assignments.get_render_resource_set_id(bind_group_descriptor.id)
|
render_resource_assignments.get_render_resource_set_id(bind_group_descriptor.id)
|
||||||
{
|
{
|
||||||
log::debug!(
|
log::trace!(
|
||||||
"start creating bind group for RenderResourceSet {:?}",
|
"start creating bind group for RenderResourceSet {:?}",
|
||||||
render_resource_set_id
|
render_resource_set_id
|
||||||
);
|
);
|
||||||
@ -132,7 +132,7 @@ impl WgpuResources {
|
|||||||
.bind_groups
|
.bind_groups
|
||||||
.insert(*render_resource_set_id, bind_group);
|
.insert(*render_resource_set_id, bind_group);
|
||||||
|
|
||||||
log::debug!(
|
log::trace!(
|
||||||
"created bind group for RenderResourceSet {:?}",
|
"created bind group for RenderResourceSet {:?}",
|
||||||
render_resource_set_id
|
render_resource_set_id
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,7 +17,4 @@ impl AppPlugin for UiPlugin {
|
|||||||
fn build(&self, app: AppBuilder) -> AppBuilder {
|
fn build(&self, app: AppBuilder) -> AppBuilder {
|
||||||
app.add_system(ui_update_system())
|
app.add_system(ui_update_system())
|
||||||
}
|
}
|
||||||
fn name(&self) -> &str {
|
|
||||||
"UI"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,59 +1,45 @@
|
|||||||
mod events;
|
mod events;
|
||||||
mod window_plugin;
|
mod window;
|
||||||
mod windows;
|
mod windows;
|
||||||
#[cfg(feature = "winit")]
|
#[cfg(feature = "winit")]
|
||||||
pub mod winit;
|
pub mod winit;
|
||||||
|
|
||||||
pub use events::*;
|
pub use events::*;
|
||||||
pub use window_plugin::*;
|
pub use window::*;
|
||||||
pub use windows::*;
|
pub use windows::*;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use crate::{
|
||||||
|
app::{plugin::AppPlugin, AppBuilder},
|
||||||
|
core::Events,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
pub struct WindowPlugin {
|
||||||
pub struct WindowId(Uuid);
|
pub primary_window: Option<WindowDescriptor>,
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
self.0.to_simple().to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Window {
|
impl Default for WindowPlugin {
|
||||||
pub id: WindowId,
|
|
||||||
pub width: u32,
|
|
||||||
pub height: u32,
|
|
||||||
pub title: String,
|
|
||||||
pub vsync: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn new(window_descriptor: &WindowDescriptor) -> Self {
|
|
||||||
Window {
|
|
||||||
id: WindowId(Uuid::new_v4()),
|
|
||||||
height: window_descriptor.height,
|
|
||||||
width: window_descriptor.width,
|
|
||||||
title: window_descriptor.title.clone(),
|
|
||||||
vsync: window_descriptor.vsync,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct WindowDescriptor {
|
|
||||||
pub width: u32,
|
|
||||||
pub height: u32,
|
|
||||||
pub title: String,
|
|
||||||
pub vsync: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WindowDescriptor {
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
WindowDescriptor {
|
WindowPlugin {
|
||||||
title: "bevy".to_string(),
|
primary_window: Some(WindowDescriptor::default()),
|
||||||
width: 1280,
|
|
||||||
height: 720,
|
|
||||||
vsync: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AppPlugin for WindowPlugin {
|
||||||
|
fn build(&self, mut app: AppBuilder) -> AppBuilder {
|
||||||
|
app = app
|
||||||
|
.add_event::<WindowResized>()
|
||||||
|
.add_event::<CreateWindow>()
|
||||||
|
.add_event::<WindowCreated>()
|
||||||
|
.add_resource(Windows::default());
|
||||||
|
|
||||||
|
if let Some(ref primary_window_descriptor) = self.primary_window {
|
||||||
|
let mut create_window_event = app.resources.get_mut::<Events<CreateWindow>>().unwrap();
|
||||||
|
create_window_event.send(CreateWindow {
|
||||||
|
descriptor: primary_window_descriptor.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/window/window.rs
Normal file
49
src/window/window.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct WindowId(Uuid);
|
||||||
|
|
||||||
|
impl WindowId {
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
self.0.to_simple().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
pub id: WindowId,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub title: String,
|
||||||
|
pub vsync: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(window_descriptor: &WindowDescriptor) -> Self {
|
||||||
|
Window {
|
||||||
|
id: WindowId(Uuid::new_v4()),
|
||||||
|
height: window_descriptor.height,
|
||||||
|
width: window_descriptor.width,
|
||||||
|
title: window_descriptor.title.clone(),
|
||||||
|
vsync: window_descriptor.vsync,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct WindowDescriptor {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub title: String,
|
||||||
|
pub vsync: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowDescriptor {
|
||||||
|
fn default() -> Self {
|
||||||
|
WindowDescriptor {
|
||||||
|
title: "bevy".to_string(),
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
vsync: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,40 +0,0 @@
|
|||||||
use super::{CreateWindow, WindowCreated, WindowDescriptor, WindowResized, Windows};
|
|
||||||
use crate::{
|
|
||||||
app::{plugin::AppPlugin, AppBuilder},
|
|
||||||
core::Events,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct WindowPlugin {
|
|
||||||
pub primary_window: Option<WindowDescriptor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WindowPlugin {
|
|
||||||
fn default() -> Self {
|
|
||||||
WindowPlugin {
|
|
||||||
primary_window: Some(WindowDescriptor::default()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppPlugin for WindowPlugin {
|
|
||||||
fn build(&self, mut app: AppBuilder) -> AppBuilder {
|
|
||||||
app = app
|
|
||||||
.add_event::<WindowResized>()
|
|
||||||
.add_event::<CreateWindow>()
|
|
||||||
.add_event::<WindowCreated>()
|
|
||||||
.add_resource(Windows::default());
|
|
||||||
|
|
||||||
if let Some(ref primary_window_descriptor) = self.primary_window {
|
|
||||||
let mut create_window_event = app.resources.get_mut::<Events<CreateWindow>>().unwrap();
|
|
||||||
create_window_event.send(CreateWindow {
|
|
||||||
descriptor: primary_window_descriptor.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Window"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -29,14 +29,9 @@ impl AppPlugin for WinitPlugin {
|
|||||||
.add_resource(WinitWindows::default())
|
.add_resource(WinitWindows::default())
|
||||||
.set_runner(winit_runner)
|
.set_runner(winit_runner)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Winit"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn winit_runner(mut app: App) {
|
pub fn winit_runner(mut app: App) {
|
||||||
env_logger::init();
|
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
let mut create_window_event_reader = app.resources.get_event_reader::<CreateWindow>();
|
let mut create_window_event_reader = app.resources.get_event_reader::<CreateWindow>();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user