sunjay / turtle

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

New Process-based Architecture #31

Closed sunjay closed 6 years ago

sunjay commented 6 years ago

Fixes #27

Originally, our architecture worked in one process with two threads: a main thread and a rendering thread. The main thread was where the Turtle was created, its commands and animations were run, and other processing took place. The rendering thread created the window (which is not thread-safe) and continuously rendered the turtle's drawings over and over again. This architecture was great because it was fairly straightforward and everything could be done in a single process.

Unfortunately, we ran into an issue (#27) on MacOS which prevents us from creating the window on a separate thread like we used to. On MacOS, we must always create and manage the window from the main thread. We don't want to introduce anymore complexity into the API of the library because it needs to be as simple as possible in order to be usable by people with any skill level. In addition to that requirement, this library must work on all major platforms (Windows, Mac, Linux Mint, Ubuntu, etc.).

So how do we accomplish both the goal of working on every major platform and keeping the library simple to use? We somehow need to have two main threads. One would be used for creating the turtle, running commands, etc. and one would do the rendering and manage the window. Since each process on an operating system can only have one main thread, the solution must be to use two processes.

This PR re-architects the crate to use two processes. The main thread (where Turtle::new() is run) spawns a new process which creates a window and runs the renderer. The processes communicate with each other using the stdin/stdout streams shared between them when the second process is spawned. We do this with Rust's built-in synchronous IO by spawning a thread in each of the two processes to continuously read from the other process.

This is all done transparently, so none of the public API changed as a result of this PR and most of the original code is still the same. Only a single new function (turtle::start()) was added to the public API in order to account for some other implementation details.

Implementation

To prepare for this PR, the master branch was made to fail with a regression test. That test made it so that new commits would all pass on Linux, but fail on MacOS.

The renderer was split into its own process and some code was added to allow the two processes to communicate with each other and manage the state correctly. We chose a pretty simple and naive querying structure on purpose in order to reduce the complexity and boilerplate required to complete this implementation.

Conceptually, these changes are actually not too complicated. The implementation took two weeks mostly because of all the random issues that came up along the way. The commit dates are a bit messed up due to rebasing.

To Do