Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The theme of my week seems to be "reduce and simplify". 🎉 😁
In #192, I took the
AccessControl
type and simplified it to remove all of the unnecessary task switching and other complexity that was introduced when it was first added in #173. While working on that PR, I realized that we could potentially removeAccessControl
entirely and radically simplify our request handling all at once.The key insights that lead to these changes are:
&mut self
can't be called concurrently with methods that are&self
(&mut self
and&self
are mutually exclusive) and B) methods with&mut self
can't be called concurrently with other methods that are&mut self
(types likeMutex
guarantee these properties when working with multiple threads)forward
,right
,go_to
, etc.) are&mut self
pen_size
,fill_color
, etc.) are&self
All of this means that:
Recall that the entire purpose of
AccessControl
was to guarantee that these two properties were upheld. While the current implementation robustly implements the IPC protocol to handle all cases, that really isn't necessary if the interface that the protocol is exposed from already enforces the correct behaviour.Clearing The Drawing
The only change in behaviour is for the unstable and currently undocumented
Drawing::clear
method that is part of the multiple turtles feature (#16). Previously, because ofAccessControl
s design, theclear
method could not get access to all of the turtles until all the animations were complete. This led to the program "waiting" on turtles to finish drawing whatever line they were currently in the middle of.This GIF demonstrates how the program pauses while it waits for the green line to finish so that it can clear the drawings:
Without
AccessControl
, the behaviour ofDrawing::clear
changes to cancel all animations and stop the turtles wherever they were whenclear
was called.This GIF shows the same program but with no pauses when clear is called:
Notice that the pink and purple lines don't reach all the way to the right as they did in the previous gif. Since the
clear
call is in the middle of a line, that line gets cancelled, the turtle stops, and then resumes drawing its remaining lines from that interrupted position.This behaviour change can be a bit subtle, but I think it's worth it to have simpler request handling code and better performance.
Implementation Notes
Instead of having separate locks for the drawing and for each turtle, we now have just a single lock for the entire
App
state. We are using synchronous locks fromparking_lot
since we don't need toawait
while holding on to the locks. Requests are all handled synchronously, in the order that they arrive. We no longer spawn a task per request.A consequence of this new design is that animations can't run in the handlers themselves since that would pause all other request handling. Instead, we get concurrent turtle animations by sending each animation to an
AnimationRunner
task. That task drives each animation to completion.The
AnimationRunner
task has to be careful to always end animations at exactly the right time. Any delays in that will cause theAnimationComplete
message to not get sent in time which will delay the next line that a turtle was going to draw. To deal with this, the task keeps track of when each animation should be updated next. It updates each animation exactly when it needs to while trying to lock the state as little as possible. Each animation is updated regularly at 60 FPS or when it ends if that is prior to the next frame.There don't appear to be any deadlocks in the loadtest anymore. This is probably because we now only have one lock over the app data and one lock over the display list. It's much easier to avoid deadlocks when you have fewer locks to order in the correct way!