Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support running Turtle logic in the browser via WebAssembly #53

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e5056ef
Introduce RenderStrategy trait.
marshallpierce Dec 9, 2017
976d5f8
Get things compiling with wasm32.
marshallpierce Dec 9, 2017
c51d470
Rename features/modules to be more representative of what they do
marshallpierce Dec 10, 2017
4e6bca8
Make Renderer (mostly) generic over piston Graphics implementations.
marshallpierce Dec 10, 2017
f80a4a7
Pull render loop out of Renderer into Server.
marshallpierce Dec 10, 2017
681eb36
Use clock abstraction because we can't use std::time in wasm
marshallpierce Dec 13, 2017
f9c1fea
WIP: turtle logic can run, but no rendering yet.
marshallpierce Dec 15, 2017
98caae5
Introduce a run_turtle! macro so we can then have alternate entry points
marshallpierce Dec 15, 2017
e0bf434
Move messenger inside desktop
marshallpierce Dec 15, 2017
121d316
Drawing background color works.
marshallpierce Dec 18, 2017
cb08af9
Get random number generation working via the browser's Math.random
marshallpierce Dec 18, 2017
8082535
Running the randomcolors example will at least render the final backg…
marshallpierce Dec 18, 2017
6e60518
Run wasm in a WebWorker and copy the buffer each time a canvas update…
marshallpierce Dec 18, 2017
db18721
Update canvas notes
marshallpierce Dec 18, 2017
6cdaab0
Let the user run any example from canvas.html
marshallpierce Dec 19, 2017
280bd9b
Remove re-exported random that depends on thread_rng and expose random
marshallpierce Dec 19, 2017
9196dc9
Render the corners of trinagles
marshallpierce Dec 19, 2017
da18b5f
Add another env function for floating point mod
marshallpierce Dec 19, 2017
2d503d7
Move event helper function into server since it's only used there
marshallpierce Dec 20, 2017
8920e3d
Rough but workable triangle rendering
marshallpierce Dec 20, 2017
8efb285
Fix silly triangle vertex indexing bug
marshallpierce Dec 20, 2017
bbfe786
Update instructions
marshallpierce Dec 21, 2017
65988eb
Load pixel array every time to tolerate heap resizing
marshallpierce Dec 21, 2017
f9c898b
Use turtle-supplied rng when generating maze
marshallpierce Dec 23, 2017
23dca11
Get tests passing (though doc tests are still failing)
marshallpierce Dec 23, 2017
997357e
Merge commit 'e58c6e2' into wasm
marshallpierce Dec 23, 2017
3aec325
Merge commit 'fcd68fc' into wasm
marshallpierce Dec 23, 2017
7d13ae0
Fix newly merged unit test
marshallpierce Dec 23, 2017
f4eba8a
Merge commit 'cd54686' into wasm
marshallpierce Dec 23, 2017
7a61384
Fix the first few doc tests to not create a Turtle.
marshallpierce Dec 23, 2017
fbea89a
Fix the rest of the doc tests.
marshallpierce Dec 24, 2017
259c102
Merge commit '5db76f7' into wasm
marshallpierce Dec 24, 2017
36effed
Merge commit '5e345e4' into wasm
marshallpierce Dec 24, 2017
3a90e00
Merge commit 'c61192d' into wasm
marshallpierce Dec 24, 2017
b49a2ee
Merge commit 'd26ad14' into wasm
marshallpierce Dec 24, 2017
81f8a85
Merge commit 'b29c274' into wasm
marshallpierce Dec 24, 2017
954a094
Merge commit '90214a8' into wasm
marshallpierce Dec 24, 2017
854c191
Merge commit '0f8519d' into wasm
marshallpierce Dec 26, 2017
74c19ec
Merge commit 'f60dc49' into wasm
marshallpierce Dec 29, 2017
b40b5d0
Merge commit '74fd197' into wasm
marshallpierce Dec 29, 2017
a875850
Merge commit 'c741084' into wasm
marshallpierce Dec 29, 2017
e67d863
Merge branch 'master' into wasm
marshallpierce Dec 29, 2017
a4f4d3b
Move debug_log out of Runtime -- not exposed via Turtle so no need fo…
marshallpierce Dec 31, 2017
83a86c2
Merge commit 'baa6021' into wasm
marshallpierce Dec 31, 2017
e040f5d
Merge commit 'b223b21' into wasm
marshallpierce Dec 31, 2017
6642ff8
Remove stray comment
marshallpierce Jan 3, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -25,3 +25,6 @@ _site/
.jekyll-metadata

# End of https://www.gitignore.io/api/jekyll

.idea
*.iml
30 changes: 30 additions & 0 deletions CANVAS.md
@@ -0,0 +1,30 @@
Turtle for the web!

# Setup

- `rustup update`
- `rustup target add wasm32-unknown-unknown --toolchain nightly`

# Building

To build wasm files for all examples, run:

```
find . -name '*.wasm' -delete && cargo +nightly build \
--no-default-features --features=canvas \
--target=wasm32-unknown-unknown --release \
--examples

```

Cargo doesn't seem very smart about updating the `.wasm` file, so we always delete it.

Once the `.wasm` file is built, run a basic web server (like `python -m SimpleHTTPServer 9000`) and open [http://localhost:9000/canvas.html](http://localhost:9000/canvas.html).

# Architecture

On a desktop OS, Turtle uses a separate process to handle rendering and turtle state. This allows the turtle control logic to take over the main thread, which in turn allows a convenient programming model: the user provides `main()`, and as long as Turtle initialization is done right, everything proceeds fine. Rendering commands, etc, are done via stdin/stdout between the control process and render process.

On the web, we don't have the built-in start point of `main()`, so control flow will have to be different. Rendering can still be done with Piston's abstraction, but we need to render to a `<canvas>`, so we need a `Graphics` implementation that writes to an RGBA pixel buffer.

The Rust logic runs in a Worker and periodically sends copies of the pixel buffer to the main thread to be displayed, which is inefficient but does work. However, there isn't a synchronous way to communicate between the Worker and the main thread, so even if we buffered up input events in the main thread, there wouldn't be a way to get them to the Rust logic since that logic never yields control of the worker thread. There are asynchronous ways to hand off data, but that would require the Rust logic halting and resuming later, which the current structure does not allow.
17 changes: 15 additions & 2 deletions Cargo.toml
Expand Up @@ -30,11 +30,24 @@ serde = "^1.0.23"
serde_derive = "^1.0.23"
serde_json = "^1.0.6"

piston_window = "^0.73.0"
piston_window = { version = "^0.73.0", optional = true }
# make sure to update low level piston dependencies when updating piston_window
piston2d-graphics = "^0.23.0"
interpolation = "^0.1.0"
rand = "^0.3.18"
pistoncore-input = "^0.20.0"

# current master has wasm32 support; released version does not
rand = { git = "https://github.com/rust-lang-nursery/rand.git", rev = "0cb85a2b9f3e0a24f1634faeb68230ec5b919340" }

[features]
default = ["desktop"]

# Render via a window on a desktop GUI OS.
desktop = ["piston_window"]

# Render via wasm to canvas. Mutually exclusive with "desktop" feature.
canvas = []

# The reason we do this is because doctests don't get cfg(test)
# See: https://github.com/rust-lang/cargo/issues/4669
#
Expand Down
43 changes: 43 additions & 0 deletions canvas.html
@@ -0,0 +1,43 @@
<html>
<head>
<title>turtle via canvas</title>
<meta charset="utf-8"/>
<script type="text/javascript">
window.onload = (_) => {
const exampleForm = document.getElementById("example");
const exampleInput = document.getElementById("example-name");
exampleForm.onsubmit = (_) => {
const worker = new Worker('worker.js');

const canvas = document.getElementById('screen');
const ctx = canvas.getContext('2d');

worker.onmessage = (e) => {
if (e.data['type'] === 'updateCanvas') {
const img = new ImageData(e.data['pixels'], canvas.width, canvas.height);
ctx.putImageData(img, 0, 0);
}
};

worker.postMessage({
'type': 'start',
'exampleName': exampleInput.value,
'width': canvas.width,
'height': canvas.height
});

return false;
};
}
</script>
</head>
<body>

<form id="example">
<input type="text" placeholder="example name" value="randomcolors" id="example-name"/>
<input type="submit"/>
</form>

<canvas id="screen" width="800px" height="600px" style="border: black 1px solid"></canvas>
</body>
</html>
7 changes: 3 additions & 4 deletions examples/arrowkeys.rs
@@ -1,11 +1,10 @@
#[macro_use]
extern crate turtle;

use turtle::Turtle;
use turtle::event::Key::{Left, Right};
use turtle::Event::KeyPressed;

fn main() {
let mut turtle = Turtle::new();
run_turtle!(|mut turtle| {

println!("Turn using the left and right arrow keys");

Expand Down Expand Up @@ -40,4 +39,4 @@ fn main() {
}
}
}
}
});
9 changes: 3 additions & 6 deletions examples/circle.rs
@@ -1,14 +1,11 @@
#[macro_use]
extern crate turtle;

use turtle::Turtle;

fn main() {
let mut turtle = Turtle::new();

run_turtle!(|mut turtle| {
for _ in 0..360 {
// Move forward three steps
turtle.forward(3.0);
// Rotate to the right (clockwise) by 1 degree
turtle.right(1.0);
}
}
});
12 changes: 6 additions & 6 deletions examples/coloredridge.rs
@@ -1,12 +1,11 @@
#[macro_use]
extern crate turtle;

use std::f64::consts::E;

use turtle::{Turtle, Color, random};

fn main() {
let mut turtle = Turtle::new();
use turtle::Color;

run_turtle!(|mut turtle| {
let amplitude = 100.0;
let width = 800.0;
let step = 2.0;
Expand All @@ -27,8 +26,9 @@ fn main() {
// 200e^(-(1/200(x - 400))^2)
let y = amplitude * E.powf(-(1.0/(width / 4.0) * (x - width/2.0)).powi(2));

turtle.set_pen_color(random::<Color>().opaque());
let pen_color = turtle.random::<Color>().opaque();
turtle.set_pen_color(pen_color);
turtle.set_pen_size(y * height_factor);
turtle.forward(step);
}
}
});
8 changes: 4 additions & 4 deletions examples/dragon.rs
Expand Up @@ -34,13 +34,13 @@
//! function, passing the direction of the turn for this fold as an angle
//! (+90 for a right turn, -90 for a left turn).

#[macro_use]
extern crate turtle;

use turtle::Turtle;
use turtle::Color;

fn main() {
let mut turtle = Turtle::new();
run_turtle!(|mut turtle| {

turtle.drawing_mut().set_background_color("#112244");

Expand All @@ -54,7 +54,7 @@ fn main() {
dragon(&mut turtle, -90., 11, 0., 255.);

turtle.hide();
}
});

/// Draw the dragon curve by simulating folding a strip of paper
///
Expand All @@ -74,7 +74,7 @@ fn dragon(
if num_folds == 0 {
// mapping a color number 0-255 to an rgb gradient.
turtle.set_pen_color(Color {
red: (color_mid - 128.).abs() * 2.,
red: (color_mid - 127.5).abs() * 2.,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this avoids a crash as the recently added color checking logic does its thing

green: color_mid,
blue: 160.,
alpha: 1.,
Expand Down
8 changes: 3 additions & 5 deletions examples/empty_star.rs
@@ -1,9 +1,7 @@
#[macro_use]
extern crate turtle;

use turtle::Turtle;

fn main() {
let mut turtle = Turtle::new();
run_turtle!(|mut turtle| {

let points = 5.0;
let angle = 180.0 / points;
Expand All @@ -23,4 +21,4 @@ fn main() {
turtle.forward(100.0);
turtle.right(180.0 - angle);
}
}
});
7 changes: 3 additions & 4 deletions examples/followmouse.rs
@@ -1,10 +1,9 @@
#[macro_use]
extern crate turtle;

use turtle::Turtle;
use turtle::Event::MouseMove;

fn main() {
let mut turtle = Turtle::new();
run_turtle!(|mut turtle| {

println!("Move your mouse around the window");

Expand All @@ -24,4 +23,4 @@ fn main() {
target = [x, y];
}
}
}
});
6 changes: 3 additions & 3 deletions examples/heart.rs
@@ -1,9 +1,9 @@
#[macro_use]
extern crate turtle;

use turtle::Turtle;

fn main() {
let mut turtle = Turtle::new();
run_turtle!(|mut turtle| {

turtle.set_speed(5);
turtle.set_pen_size(3.0);
Expand All @@ -28,7 +28,7 @@ fn main() {
end_loop(&mut turtle);

turtle.drawing_mut().set_background_color("pink");
}
});

fn curve(turtle: &mut Turtle) {
for _ in 0..100 {
Expand Down
7 changes: 3 additions & 4 deletions examples/logo_interpreter.rs
@@ -1,16 +1,15 @@
//! To run a LOGO program, run:
//! cargo run --example logo_interpreter -- my_logo_program.txt

#[macro_use]
extern crate turtle;

use turtle::Turtle;
use std::env;
use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn main() {
let mut turtle = Turtle::new();

run_turtle!(|mut turtle| {
let args: Vec<String> = env::args().collect();

if args.len() != 2 {
Expand All @@ -23,7 +22,7 @@ fn main() {
let file = File::open(path).expect("Could not open provided program");
interpret(&mut turtle, file)
}
}
});

fn interpret<R: io::Read>(turtle: &mut Turtle, input: R) {
let mut reader = BufReader::new(input);
Expand Down
12 changes: 0 additions & 12 deletions examples/maze/grid.rs
@@ -1,7 +1,5 @@
use std::ops::{Index, IndexMut};

use turtle::random;

use cell::Cell;
use wall::Wall;

Expand Down Expand Up @@ -161,16 +159,6 @@ impl Grid {
Grid([Cells::default(); GRID_SIZE])
}

pub fn random_walls() -> Grid {
let mut grid = [Cells::default(); GRID_SIZE];
for row in &mut grid {
for cell in row {
*cell = random();
}
}
Grid(grid)
}

/// Returns true if there is NO wall between two adjacent cells
pub fn is_open_between(
&self,
Expand Down
9 changes: 4 additions & 5 deletions examples/maze/main.rs
Expand Up @@ -4,6 +4,7 @@ mod maze;
mod grid;
mod solver;

#[macro_use]
extern crate turtle;

use turtle::{Turtle, Color};
Expand All @@ -16,12 +17,10 @@ use solver::solve;
const WIDTH: f64 = 600.0; // px
const HEIGHT: f64 = 600.0; // px

fn main() {
turtle::start();
run_turtle!(|mut turtle| {

let maze = Maze::generate();
let maze = maze::generate(&mut turtle.rng());

let mut turtle = Turtle::new();
turtle.set_speed(8);
turtle.drawing_mut().set_background_color("#BDBDBD");
turtle.set_pen_color("#03A9F4");
Expand Down Expand Up @@ -74,7 +73,7 @@ fn main() {
turtle.set_speed(5);
turtle.set_pen_size(2.0);
solve(&mut turtle, maze, cell_width, cell_height);
}
});

fn draw_rows<
'a,
Expand Down