Week 05 Workshop
Exercise:
Workshop 5 - So.. Physics. Physics, Eh? Physics physics
physics physics...
The Workshop
This week's workshop will explore Rust's trait system in more detail. You have been provided with code that does not use traits at all, which simulates the motion of bodies under gravity. Your task will be to gradually refactor the code towards using traits, rather than an enum.
Task 1: Understanding The Code You Have Been Provided
In the library you have been provided, there are two types of
object defined in an enum.
Planet
s do not move, but apply gravity to other
objects. Asteroid
s move with a certain initial
velocity, and are affected by gravity.
Since the enum is defined by the library, it is not possible to extend the library's behaviour to include different object types. In this task, you will modify the code so that it can support user-defined objects.
Task 2: Starting the simulator
In your starter code, you have been provided an HTML file called "phys_simulator.html". Open this file in your web browser to see a simulation of planets orbiting a star on your screen. You will see the small dots (asteroids), orbiting the large dot (planet).
Task 3: Removing the Enum
In the current code, you've been provided the
ObjectType
enum. As a user of the library, this
gives you very little flexibility on what you can simulate: you
are limited to asteroids and planets. In this workshop, we will
be making the library more flexible, such that a user could
implement their own types of celestial objects.
For the moment, we'll be changing our code so we can model objects which are affected by gravity, and which provide gravity. By the end of the tutorial, we'll be able to model any object that does both, but for now this allows us to make small changes to our code in each step.
Therefore, refactor the code so that rather than taking a vec of
enums, it takes a vec of Planets, and a vec of Asteroids. Once
you are done, you should be able to entirely remove the
ObjectType
enum.
Task 4: Defining Shared Behaviour
You'll notice that both Planets and Asteroids share code which
defines their position, and converts them into a
Circle
struct to be sent to the front-end. This is
shared behaviour which we can use a trait to represent.
Refactor the code so that Planets and Asteroids share a trait which defines their position, and allows conversion into a Circle.
Task 5: Refactoring Planets
Now we are ready to refactor our code to have the relevant code for Planets be more general. Refactor the code by defining a trait which means that any object which is a "gravity source" can be passed in the place of a planet.
To test our refactoring, we're going to implement a new type of gravity source which is not a planet. This type of gravity source should pulse (i.e. have high gravity, then low gravity, then high again).
Task 6: Refactoring Asteroids
Similarly to task 5, refactor the code so that any object which is a "gravity receiver" can be passed in the place of an Asteroid.
Implement an Asteroid which is only affected by gravity when it's further away than 100 units from a gravity source.
Task 7: (Extension) Combining Gravitational Objects With One Trait
This task is an extension, and requires a little bit of "outside the Box" thinking (hey, it's a pun!). So far, we've made Gravity Sources and Gravity Receivers be two seperate traits. Rust's ownership model means that something cannot be both a source and a receiver. In order to fix this, we can make a new trait called something like GravityObject, which allows the implementor to declare whether an object is a source, receiver, or both.
To do this, write a trait which has two functions. One function
should return an
Option<&dyn GravitySource>
, the other
should return a
Option<&dyn GravityReceiver>
. You can
then implement this function on objects which implement either
trait, to help figure out what sort of object they are.
Once you've done this, the code can take a single vector of GravityObjects, rather than one of Sources and one of Receivers.