
# Objective - Fixes #17960 ## Solution - Followed the [edition upgrade guide](https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html) ## Testing - CI --- ## Summary of Changes ### Documentation Indentation When using lists in documentation, proper indentation is now linted for. This means subsequent lines within the same list item must start at the same indentation level as the item. ```rust /* Valid */ /// - Item 1 /// Run-on sentence. /// - Item 2 struct Foo; /* Invalid */ /// - Item 1 /// Run-on sentence. /// - Item 2 struct Foo; ``` ### Implicit `!` to `()` Conversion `!` (the never return type, returned by `panic!`, etc.) no longer implicitly converts to `()`. This is particularly painful for systems with `todo!` or `panic!` statements, as they will no longer be functions returning `()` (or `Result<()>`), making them invalid systems for functions like `add_systems`. The ideal fix would be to accept functions returning `!` (or rather, _not_ returning), but this is blocked on the [stabilisation of the `!` type itself](https://doc.rust-lang.org/std/primitive.never.html), which is not done. The "simple" fix would be to add an explicit `-> ()` to system signatures (e.g., `|| { todo!() }` becomes `|| -> () { todo!() }`). However, this is _also_ banned, as there is an existing lint which (IMO, incorrectly) marks this as an unnecessary annotation. So, the "fix" (read: workaround) is to put these kinds of `|| -> ! { ... }` closuers into variables and give the variable an explicit type (e.g., `fn()`). ```rust // Valid let system: fn() = || todo!("Not implemented yet!"); app.add_systems(..., system); // Invalid app.add_systems(..., || todo!("Not implemented yet!")); ``` ### Temporary Variable Lifetimes The order in which temporary variables are dropped has changed. The simple fix here is _usually_ to just assign temporaries to a named variable before use. ### `gen` is a keyword We can no longer use the name `gen` as it is reserved for a future generator syntax. This involved replacing uses of the name `gen` with `r#gen` (the raw-identifier syntax). ### Formatting has changed Use statements have had the order of imports changed, causing a substantial +/-3,000 diff when applied. For now, I have opted-out of this change by amending `rustfmt.toml` ```toml style_edition = "2021" ``` This preserves the original formatting for now, reducing the size of this PR. It would be a simple followup to update this to 2024 and run `cargo fmt`. ### New `use<>` Opt-Out Syntax Lifetimes are now implicitly included in RPIT types. There was a handful of instances where it needed to be added to satisfy the borrow checker, but there may be more cases where it _should_ be added to avoid breakages in user code. ### `MyUnitStruct { .. }` is an invalid pattern Previously, you could match against unit structs (and unit enum variants) with a `{ .. }` destructuring. This is no longer valid. ### Pretty much every use of `ref` and `mut` are gone Pattern binding has changed to the point where these terms are largely unused now. They still serve a purpose, but it is far more niche now. ### `iter::repeat(...).take(...)` is bad New lint recommends using the more explicit `iter::repeat_n(..., ...)` instead. ## Migration Guide The lifetimes of functions using return-position impl-trait (RPIT) are likely _more_ conservative than they had been previously. If you encounter lifetime issues with such a function, please create an issue to investigate the addition of `+ use<...>`. ## Notes - Check the individual commits for a clearer breakdown for what _actually_ changed. --------- Co-authored-by: François Mockers <francois.mockers@vleue.com>
145 lines
5.1 KiB
Rust
145 lines
5.1 KiB
Rust
//! Example of how to draw to a texture from the CPU.
|
|
//!
|
|
//! You can set the values of individual pixels to whatever you want.
|
|
//! Bevy provides user-friendly APIs that work with [`Color`](bevy::color::Color)
|
|
//! values and automatically perform any necessary conversions and encoding
|
|
//! into the texture's native pixel format.
|
|
|
|
use bevy::color::{color_difference::EuclideanDistance, palettes::css};
|
|
use bevy::prelude::*;
|
|
use bevy::render::{
|
|
render_asset::RenderAssetUsages,
|
|
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
|
};
|
|
use rand::{Rng, SeedableRng};
|
|
use rand_chacha::ChaCha8Rng;
|
|
|
|
const IMAGE_WIDTH: u32 = 256;
|
|
const IMAGE_HEIGHT: u32 = 256;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
// In this example, we will use a fixed timestep to draw a pattern on the screen
|
|
// one pixel at a time, so the pattern will gradually emerge over time, and
|
|
// the speed at which it appears is not tied to the framerate.
|
|
// Let's make the fixed update very fast, so it doesn't take too long. :)
|
|
.insert_resource(Time::<Fixed>::from_hz(1024.0))
|
|
.add_systems(Startup, setup)
|
|
.add_systems(FixedUpdate, draw)
|
|
.run();
|
|
}
|
|
|
|
/// Store the image handle that we will draw to, here.
|
|
#[derive(Resource)]
|
|
struct MyProcGenImage(Handle<Image>);
|
|
|
|
#[derive(Resource)]
|
|
struct SeededRng(ChaCha8Rng);
|
|
|
|
fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
|
|
commands.spawn(Camera2d);
|
|
|
|
// Create an image that we are going to draw into
|
|
let mut image = Image::new_fill(
|
|
// 2D image of size 256x256
|
|
Extent3d {
|
|
width: IMAGE_WIDTH,
|
|
height: IMAGE_HEIGHT,
|
|
depth_or_array_layers: 1,
|
|
},
|
|
TextureDimension::D2,
|
|
// Initialize it with a beige color
|
|
&(css::BEIGE.to_u8_array()),
|
|
// Use the same encoding as the color we set
|
|
TextureFormat::Rgba8UnormSrgb,
|
|
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
|
);
|
|
|
|
// To make it extra fancy, we can set the Alpha of each pixel,
|
|
// so that it fades out in a circular fashion.
|
|
for y in 0..IMAGE_HEIGHT {
|
|
for x in 0..IMAGE_WIDTH {
|
|
let center = Vec2::new(IMAGE_WIDTH as f32 / 2.0, IMAGE_HEIGHT as f32 / 2.0);
|
|
let max_radius = IMAGE_HEIGHT.min(IMAGE_WIDTH) as f32 / 2.0;
|
|
let r = Vec2::new(x as f32, y as f32).distance(center);
|
|
let a = 1.0 - (r / max_radius).clamp(0.0, 1.0);
|
|
|
|
// Here we will set the A value by accessing the raw data bytes.
|
|
// (it is the 4th byte of each pixel, as per our `TextureFormat`)
|
|
|
|
// Find our pixel by its coordinates
|
|
let pixel_bytes = image.pixel_bytes_mut(UVec3::new(x, y, 0)).unwrap();
|
|
// Convert our f32 to u8
|
|
pixel_bytes[3] = (a * u8::MAX as f32) as u8;
|
|
}
|
|
}
|
|
|
|
// Add it to Bevy's assets, so it can be used for rendering
|
|
// this will give us a handle we can use
|
|
// (to display it in a sprite, or as part of UI, etc.)
|
|
let handle = images.add(image);
|
|
|
|
// Create a sprite entity using our image
|
|
commands.spawn(Sprite::from_image(handle.clone()));
|
|
commands.insert_resource(MyProcGenImage(handle));
|
|
|
|
// We're seeding the PRNG here to make this example deterministic for testing purposes.
|
|
// This isn't strictly required in practical use unless you need your app to be deterministic.
|
|
let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
|
|
commands.insert_resource(SeededRng(seeded_rng));
|
|
}
|
|
|
|
/// Every fixed update tick, draw one more pixel to make a spiral pattern
|
|
fn draw(
|
|
my_handle: Res<MyProcGenImage>,
|
|
mut images: ResMut<Assets<Image>>,
|
|
// Used to keep track of where we are
|
|
mut i: Local<u32>,
|
|
mut draw_color: Local<Color>,
|
|
mut seeded_rng: ResMut<SeededRng>,
|
|
) {
|
|
if *i == 0 {
|
|
// Generate a random color on first run.
|
|
*draw_color = Color::linear_rgb(
|
|
seeded_rng.0.r#gen(),
|
|
seeded_rng.0.r#gen(),
|
|
seeded_rng.0.r#gen(),
|
|
);
|
|
}
|
|
|
|
// Get the image from Bevy's asset storage.
|
|
let image = images.get_mut(&my_handle.0).expect("Image not found");
|
|
|
|
// Compute the position of the pixel to draw.
|
|
|
|
let center = Vec2::new(IMAGE_WIDTH as f32 / 2.0, IMAGE_HEIGHT as f32 / 2.0);
|
|
let max_radius = IMAGE_HEIGHT.min(IMAGE_WIDTH) as f32 / 2.0;
|
|
let rot_speed = 0.0123;
|
|
let period = 0.12345;
|
|
|
|
let r = ops::sin(*i as f32 * period) * max_radius;
|
|
let xy = Vec2::from_angle(*i as f32 * rot_speed) * r + center;
|
|
let (x, y) = (xy.x as u32, xy.y as u32);
|
|
|
|
// Get the old color of that pixel.
|
|
let old_color = image.get_color_at(x, y).unwrap();
|
|
|
|
// If the old color is our current color, change our drawing color.
|
|
let tolerance = 1.0 / 255.0;
|
|
if old_color.distance(&draw_color) <= tolerance {
|
|
*draw_color = Color::linear_rgb(
|
|
seeded_rng.0.r#gen(),
|
|
seeded_rng.0.r#gen(),
|
|
seeded_rng.0.r#gen(),
|
|
);
|
|
}
|
|
|
|
// Set the new color, but keep old alpha value from image.
|
|
image
|
|
.set_color_at(x, y, draw_color.with_alpha(old_color.alpha()))
|
|
.unwrap();
|
|
|
|
*i += 1;
|
|
}
|