Topics:

Wheel of Names with Bevy via @DanielPBank

Repo: https://github.com/danielbank/ferris-wheel

I'm attempting to build a Rust port of the Wheel of Names JS App using Bevy Game Engine. Currently, Bevy does not support drawing custom shapes in an easy way. Fortunately, there is a Bevy plugin called, bevy_prototype_lyon which provides this functionality using lyon, which implements 2D Graphics rendering on the GPU using tessellation.

Make a Wheel

I started with the bevy_prototype_lyon example code and made a circle. The code for this is straightforward

let wheel = shapes::Circle {
    radius: 200.0,
    ..shapes::Circle::default()
};

commands.spawn_bundle(GeometryBuilder::build_as(
    &wheel,
    ShapeColors::outlined(Color::TOMATO, Color::BLACK),
    DrawMode::Outlined {
        fill_options: FillOptions::default(),
        outline_options: StrokeOptions::default().with_line_width(10.0),
    },
    Transform::default(),
));

Make the Wheel Spin

To figure out how to do motion, I copied from the Bevy Breakout Game example, here I learned some things that surprised me.

Query Components Not Entities

Bevy follows the Entity Component System paradigm so we need to understand a little bit about that in order to move things. Specifically to move an entity, we need to first query for a component of that specific entity before applying transformations:

if let Ok((ball, mut transform)) = ball_query.single_mut() {
    transform.translation += ball.velocity * delta_seconds;
}

Naively, we may want to try to do something with the Shapes (e.g. wheel = shapes::Circle { ... }) or the ShapeBundles (e.g. GeometryBuilder::build_as( ... )) to get them to move. However, Bevy wants us to interact with Components which are structs attached to Entities. We attach these components to entities by calling .insert() on the ShapeBundle we get back from GeometryBuilder::build_as( ... ).

commands
    .spawn_bundle(GeometryBuilder::build_as( ... )
    .insert(Component {});

The components could even be an empty structs, we simply need something to get a handle on when we query the system for things to transform.

Since I wanted to apply my rotation movement to all the Entities in my app, I didn't use single_mut(), which matches a single entity, and instead iterated over:

particles_query
  .iter_mut()
  .for_each(|(particle, mut transform)| {
      transform.rotate(Quat::from_rotation_z(FRAC_PI_2));
  });

Caveat about Getting Past an Initial wgpu Validation Error

When I first tried running the bevy_prototype_lyon example code, I was getting the following error:

wgpu error: Validation Error

Caused by:
    In a RenderPass
      note: encoder = `<CommandBuffer-(1, 2, Metal)>`
    In a pass parameter
      note: command buffer = `<CommandBuffer-(1, 2, Metal)>`
    attachment's sample count 8 is invalid

I fixed this issue by changing the Msaa { samples: 8 } to Msaa { samples: 4 }. Some light reading on Multisample anti-aliasing revealed that most modern GPUs support 2×, 4×, and 8× MSAA samples. It looks like my 2015 MacBook Pro is starting to show its age.

Embedded Graphics Updates via @jacobrosenthal

The embedded-graphics crate is getting close to a 0.7.0 release and lots of cool stuff is now possible.

The Pygamer Board Support Crate already uses (an older version of) embedded-graphics, so a lot of these recent advancements can hopefully be made available in it soon.

Enforcing Api Specs with Traits via @JesusGuzmanJr

Jesus demonstrates how to use traits to enforce restful apis.

Motivation

I've been experimenting with Rust to gauge its suitability as a fullstack programming language for web dev shops. How can Rust help a team of developers write code that is easy to maintain and less buggy?

One way to do this is by leveraging Rust's type system.

This tutorial assumes that you have familiarity with an http web framework like flask, Django, Spring, Vertx, gin etc.

Aside: please excuse the mal-formamted code. I attempted condensed the line width to make it more legible on narrower screens.

Say I have a fullstack webapp composed of 3 crates:

  • common - common code that's shared by other crates
  • webapp - webapp code that runs in browser
  • server - server code that runs on cloud

First I defined a Fetch trait. When a type implements Fetch, that means it can be fetched from my server. In common/src/fetch.rs:

pub trait Fetch { // implement for a Response type
    type Request; // the associated Request type
    const RATE_LIMIT_SEC: usize;
    const ENABLE_ANTICSRF_PROTECTION: bool;
    fn path() -> Path;
}

Now I can define the types for a particular endpoint of my api. In common/src/api/user.rs:

pub struct Request { // (1)
    pub username: Username,
    pub email: Email,
    pub password: Password,
}

pub enum Response {
    Created(User),
    UsernameUnavailable,
    EmailUnavailable,
}

impl Fetch for Response {
    // the associated Request type is Request (1)
    type Request = Request;
    const ENABLE_ANTICSRF_PROTECTION: bool = true;
    const RATE_LIMIT_SEC: usize = 3;

    fn path() -> Path {
        Path::CreateUser
    }
}

Now that we have a Fetch type, we need to define a fetch function to use Fetch types. Let's jump to our webapp. In webapp/src/request:

pub async fn fetch<Req, Res>(request: &Req) -> Result<Res, Error>
where
    Req: Serialize, // (1)

              // (2)     (3)
    Res: Fetch<Request = Req> + DeserializeOwned,

{
    use seed::browser::fetch::Request as HttpRequest;
    let mut request = HttpRequest::new(Res::path())
        // I use POST for all apis :P
        .method(Method::Post)
        .bytes(rmp_serde::to_vec(request)?) // (1) required here
        .header(build_message_pack_header());

    // Since `Res` is `Fetch`
    // we can grab any of the associated items like
    // associated types or
    // associated constants
    // from the `Fetch` trait

    // In this case we grab the associated boolean constant
    if Res::ENABLE_ANTICSRF_PROTECTION {
        // add the anti-cross-site-request-forgery header
    }

    // the request function comes from Seed-rs
    let bytes = request
        .fetch()
        .await?
        // I use 200 OK for apis :P
        .check_status()?
        .bytes()
        .await?
        .as_slice();

    let response = rmp_serde::from_read_ref(bytes)?;

    Ok(response)
}
  1. Notice how fetch<Req, Res>(_: &Req) -> Result<Res, Error> where ... is generic over both a request and respond type. The only constraint on the request parameter is that it must be serializable (1)
  2. The constraint of the response type is that is must be Fetch and its associated Request type (3) must be the same type as the input parameter (4)

Let's learn how to call this fetch function to make http requests from our webapp to our server. In webapp/srs/pages/signup.rs:

// create our request object
let request = api::user::Request {
    // ~snip~
};

// and just fetch it!
let response = request::fetch(&request).await;

// make some matcha while we're at it!
match response {
    Err(error) => {
        seed::log(error); // log to browser console :(
    }
    Ok(Response::Created(_newborn_user)) => ()
    Ok(Response::UsernameUnavailable) => ()
    Ok(Response::EmailUnavailable) => ()
}

Now let's take a look at our server. In server/src/responder.rs:

#[async_trait::async_trait]
pub trait FetchResponder: Fetch + Sized { // (1)
    async fn respond(request: Self::Request) -> Result<Self, Error>;
}

We define a new trait called FetchResponder. Any type that is FetchResponder can be responded with as the payload of an http response.

  1. Also notice that FetchResponder can only be implemented on types that are Fetch (1). That means that a type that is FetchResponder is also Fetch.

Recall that we would normally create an http request handler for each of our api items. While this process is not difficult, it is tedious and requires manual work. (E.g. read the api doc and construct the proper http response to send back)

To improve upon this situation, we'll define a generic request handler that implements the http, non-variable parts or our api while leaving generic the variable, api types.

//                  (1)
pub async fn respond<F>(request: HttpRequest, bytes: Bytes) -> Result<HttpResponse, Error>
where
    F: FetchResponder + Serialize, // (1)

    <F as Fetch>::Request: DeserializeOwned + Validate, // (2)

    <<F as Fetch>::Request as Validate>::Invalidity: Serialize, // (3)
{
    if <F as Fetch>::ENABLE_ANTICSRF_PROTECTION {
        // perform anti-cross-site-request forgery checks
        // ~snip~
    }

    // (4)
    let rate_limit_sec = <F as Fetch>::RATE_LIMIT_SEC;

    // perform rate limiting
    // ~snip~

    // my server can handle payloads of various content types :P
    let format = Format::from(&request);

    //                                            (5)
    match deserialize::deserialize::<<F as Fetch>::Request>(&bytes, format) {
        Err(error) => Ok(
            response::Builder::error(
                StatusCode::BAD_REQUEST, error
            )
        ),

        Ok(request) => {
            //                         `Validate` required here
            //                                                |
            //                                                v
            let response = if let Err(invalidities) = request.validate() {
                response::Builder::error(
                    StatusCode::BAD_REQUEST, &invalidities
                )
            } else {
//  `Invalidity: Serialize` required here
//                                      |
//                                      v
                response::Builder::ok().serialize(
                    &F::respond(request).await?, format // (5)
                )?
            };
            Ok(response)
        }
    }
}

  1. Notice how respond<F>(_: HttpRequest, _: Bytes) -> Result<HttpResponse, Error> where ... is generic over F yet F is neither found as a parameter nor return type! When I first saw this, I thought it was black-magic! But it's not. In the where clause we state the trait bound: F must be FetchResponder and Serialize.

  2. This is where it gets interesting. The type F is FetchResponder, but recall the definition of FetchResponder. Any type that is FetchResponder is also Fetch! The generic type F that we use in this function call must implement both traits. Because of this, we can cast F into the supertrait Fetch and grab an associated item. In this case we are constraining the associated type Request of the trait Fetch of the subtrait FetchResponder of the generic type F to be both DeserializeOwned and Validate.

    Note that Validate is a trait I created that is outside the scope of this discussion. The trait bound is required so I can validate the request payload.

  3. Also outside the scope of this discussion. Here I'm bounding the Invalidity type of the Request type to be Serialize.

  4. We get the associated constant RATE_LIMIT_SEC from the Fetch trait.

Now that we have defined FetchResponder and shown how it is used, lets implement it on our Response type. Recall that Response as defined in common/src/api/user.rs looks like:

pub enum Response {
    Created(User),
    UsernameUnavailable,
    EmailUnavailable,
}

Now back to our server in server/src/route/user.rs:

#[async_trait::async_trait]
impl FetchResponder for Response {

    async fn respond(

        Request {
            name,
            username,
            email,
            password,
            language,
        }: Request,

    ) -> Result<Response, Error> {

        type PgError = persistance::postgres::Error;
        let hashed_password = security::hash::hash_password(&password)?;

        match persistance::user::create(
                &name,
                &username,
                &email,
                &hashed_password,
                language
            ).await
        {
            Err(
                persistance::Error::PostgresError(
                        PgError::UniqueViolation(violation)
                    )
                )
                if violation.constraint() == Some(
                    persistance::user::USERNAME_CONTRAINT
                ) =>
            {
                Ok(Response::UsernameUnavailable)
            }


            Err(
                persistance::Error::PostgresError(
                        PgError::UniqueViolation(violation)
                    )
                )
                if violation.constraint() == Some(
                    persistance::user::EMAIL_CONTRAINT
                ) =>
            {
                Ok(Response::EmailUnavailable)
            }

            Err(error) => Err(error.into()),

            Ok((user, _credentials)) => Ok(Response::Created(user)),
        }
    }
}

Notice the lack of http types. E.g. We don't have to worry abut http status codes. No json, nada of the sort. Instead we focus on calling our database and doing some matching to figure out what the db is saying.

The last part is to register our http handler with actix_web, my web framework of choice. In server/src/route.rs:

macro_rules! fetch_responder {
    ($config:ident, $fetch:ty) => {
        let path = <$fetch as Fetch>::path(); // (2)
        let resource = Resource::new(path);
        let responder = responder::respond::<$fetch>; // (3)

        // I use POST methods for all apis :P
        let route = Route::new().method(Method::POST).to(responder);
        $config.service(resource.route(route));
    };
}

pub fn routes(config: &mut use actix_web::web::ServiceConfig) {
    // ~snip~
    fetch_responder!(config, api::user::Response); // (1)
    // ~snip~
}
  1. Recall that Response is FetchResponder and Fetch. We pass this api type into our macro.
  2. We cast the Response type as Fetch and call the trait's path() function to get the url path. Because this method was implemented in our common crate where our api lives, any change to our api, will automatically propagate here like magic!
  3. Here is where our respond<F>(...) function gets specialized. The generic F now becomes Response. Actix will call the responder whenever an http request hits our path!

That's it folks!

Summary

We used traits to create required items (functions, types and constants) on elements of our api that vary per api endpoint. We then proceeded to abstract away the non-variable api aspects (e.g. the response code, http method, payload content type) to let our request handlers focus singularly on the aspect of the api they care about (in our case it was writing to the db).

By leveraging Rust's type system, we can build a full-stack app that is amenable to changing business requirements and easily maintainable by newbies like me or Rusty gurus like you.

Cheers!

Jesus

Making a Tiny Game with Raylib via @mysteriouspants

Chris is participating in the 4MB Game Jam this month. As per the name, game submissions need to be smaller than 4mb. Chris is making his game with Rust using the raylib crate, which provides Rust bindings for raylib, a C library for making video games. Part of the fun is optimizations, including trimming down raylib to not include modules that he isn't using. The full raylib library is around 1MB in size, but by trimming out what he isn't using, Chris can get the final dependency to only 40KB!

Crates You Should Know

  • lyon: 2D Graphics rendering on the GPU using tessellation
  • bevy_prototype_lyon: Draw 2D shapes and paths in the Bevy game engine
  • raylib: Safe Rust bindings for Raylib