sunjay / turtle

Create Animated Drawings in Rust
http://turtle.rs
Mozilla Public License 2.0
559 stars 54 forks source link

Rewrite and Simplify AccessControl #192

Closed sunjay closed 3 years ago

sunjay commented 3 years ago

This PR rewrites AccessControl as a data structure that does not require any internal Tokio tasks to manage the state of the turtles and drawing. It is a radically simplified version of the previous AccessControl type that accomplishes all of the same goals. It also splits the code between multiple files, hopefully making it a bit easier to read and understand.

The existing version of AccessControl was focused on correctness and figuring out how to implement the right behaviour. That's why it used a separate task to represent each resource and channels to communicate with that resource. While that mental model is correct and easy to understand, doing a ton of async communication where a boolean would also suffice is very wasteful.

This version builds on that work and creates a simple data structure that is both correct and performant. The current status of each resource is kept using a single boolean. A queue of pending requests is only utilized if the resource is not already available. The common case where each request only needs a single resource is now very fast. There are far fewer steps between requesting a resource and getting access to it, especially if that resource is already available.

The API is now fully type-safe and driven by generics and traits. Many of the runtime checks that were previously necessary are now completely gone. For example, consider the following code from one of our handlers:

let mut data = app_control.get(RequiredData {
    drawing: false,
    turtles: Some(RequiredTurtles::One(id)),
}, data_req_queued).await;
let mut turtles = data.turtles_mut().await;

let TurtleDrawings {state: turtle, current_fill_polygon, ..} = turtles.one_mut();

This requests access to a single turtle. The API however doesn't know how much you're requesting, so you're forced to pretend like there could be many turtles (e.g. turtles_mut()). You're then forced to assert that only a single turtle was returned (e.g. one_mut()) even though you already know that to be the case. This is very error prone and easy to mess up.

The new API looks as follows:

let turtle = app_control.get(id, data_req_queued).await;
let turtle = turtle.lock().await;
let TurtleDrawings {state: turtle, current_fill_polygon, ..} = &*turtle;

Here, we use the type system (generics and traits) to encode that only a single turtle is requested and only a single turtle will be returned. Even the code inside get is simplified and you no longer need to even check if the drawing was requested in cases when it isn't. No runtime checks are required because the API knows exactly what you are going to get back.