Empirical: Compile & Run for the Web
The web tools in Empirical are structured to allow the developer to fully control components of a web page from C++.
Empirical web Widgets include Text, Buttons, Images, Tables, or many other HTML components.
All widgets are derived from emp::Widget
and structured such that multiple widget objects can properly refer to and modify the same underlying DOM component.
đź”— Installing the Emscripten Compiler
To compile for web, you’ll need the Emscripten LLVM-to-Web Compiler. If you’re a new user, you (probably) don’t have this set up so we’ll walk you through step-by-step.
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install 3.1.2
./emsdk activate 3.1.2
When you want to use the Emscripten compiler, you’ll want to hop over to the emsdk
directory and run
source ./emsdk_env.sh
to load Emscripten’s odds and ends into your PATH
.
You only need to do this the first time you want to use Emscripten in a particular terminal session.
Be sure to remember to do this source
chore every time you start a new terminal session and want to use Emscripten.
You’ll also need to make sure you have at least Python 3.5 (or at least 2.7.12 if using Python2). You can check your Python version like this:
python3 --version
python --version
If you run into Python issues during the install, this thread is a great reference with lots of different fixes to try.
đź”— Hello, Browser!
Get. Psyched. To. Compile. For. Web.
There’s three main components at play here:
- some C++ code we want to execute in the web browser,
- the JavaScript-compatible executable that the compiler turns our source into, and
- a HTML file which grabs that executable into the web browser and provides some baseline structure for the executable to interact with.
Assuming you haven’t already cloned down Empirical, let’s get your working environment all set.
git clone --recursive https://github.com/devosoft/Empirical
Now, let’s make a new directory to work in.
mkdir hello-world
cd hello-world
Let’s make some starter code.
main.cpp
#include "emp/web/web.hpp"
emp::web::Document doc("target");
int main() {
doc << "<h1>Hello, world!</h1>";
}
What’s going on here?
The line
#include "emp/web/web.hpp"
brings in Empirical’s web tools, which provide a convenient interface for C++ code to interact with browser-y bits like HTML and JavaScript.
The line
emp::web::Document doc("target");
creates a persistent emp::web::Document
object (i.e., outside the scope of the main function) that hooks into the target
div in a HTML file.
Then, in main
, we write our message to the target
div (wrapped in some HTML markup formatting).
Let’s compile! If you’re curious what all these flags do, take a look here.
em++ -std=c++17 -I../Empirical/include/ -Os --js-library ../Empirical/include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback', '_empDoCppCallback']" -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" -s NO_EXIT_RUNTIME=1 main.cpp -o main.js
We should now have main.js
and main.wasm
ready to go.
You can verify this by entering ls
at your command line.
đź”— Make it Play in the Browser
Next, we just need a quick HTML file to serve up.
index.html
<body>
<div id="target"> </div>
</body>
<script src="https://code.jquery.com/jquery-1.11.2.min.js" integrity="sha256-Ls0pXSlb7AYs7evhd+VLnWsZ/AqEHcXBeMZUycz/CcA=" crossorigin="anonymous"></script>
<script type="text/javascript" src="main.js"></script>
Python provides a handy, no-hassle tool serve a website locally.
Try running
python3 -m http.server
at your command line. If it starts up, then great! Just leave it running for now.
If you only have Python 2 installed, try running
python -m SimpleHTTPServer
at your command line.
Pop open your favorite browser and point the address bar to http://localhost:8000/.
Voila!
You can end your web serving process by closing the terminal window you’re working in or entering <ctrl>-c
at the command line.
đź”— Controlling a Web Page
To understand how most of Empirical’s HTML widgets work we need only change the main code file; the current HTML file and compiler options can be left the same.
As indicated by our starting point, text can be streamed into an HTML document in a similar way to an output stream in the standard library. For example, we can update our main function to mix text and variables:
int main() {
int x = 5;
doc << "<h1>Hello World!</h1>";
doc << "x = " << x << ".<br>";
}
In additional to regular variables, emp::Document
(and other Empirical
web containers) can also take a range of Empirical Widgets.
void Ping() { doc << "Ping! "; }
int main() {
int x = 5;
doc << "<h1>Hello World!</h1>";
doc << "x = " << x << ".<br>";
// Insert an image (in place)
doc << emp::web::Image("http://www.nyan.cat/cats/original.gif") << "<br>";
// Create a button and then insert it.
emp::web::Button my_button( Ping, "Click me!" );
doc << my_button;
}
Notice now that you not only have a pretty picture, but you also have a button that will add new text on to the screen each time it’s clicked.
But what if we want to update existing content? We can do this in two ways: either by marking a variable (or function) as “Live” or by simply changing a widget that is already on the screen.
🔗 “Live” Variables
Anything sent to a web page that is inside a emp::web::Live()
function will always have its most current value used whenever that portion of the page is redrawn.
For example, let’s make our button modify the value of x
and redraw it.
int x = 5;
int main() {
doc << "<h1>Hello World!</h1>";
doc << "Original x = " << x << ".<br>";
doc << "Current x = " << emp::web::Live(x) << ".<br>";
// Create a button to modify x.
emp::web::Button my_button( [](){ x+=5; doc.Redraw(); }, "Click me!" );
doc << my_button;
}
Notice that we also moved x
to be a global variable.
This is because if it were local to main()
it would be freed as soon as main
ended.
We’re also using a lambda this time instead of a previously defined function.
Either option is fine.
Try clicking on the button.
You’ll see that x
will be updated, and then the document is signaled that it needs to redraw, so the change is reflected on the screen.
Note that we didn’t actually need to redraw the entire document to update x
, just the Text
widget it is in; we’ll talk more about how to do that below.
Of course, we can put a function in the emp::web::Live()
and that function will be called each time the containing Widget is redrawn.
int x = 5;
int main() {
doc << "<h1>Hello World!</h1>";
doc << "Original x = " << x << ".<br>";
doc << "Current x = " << emp::web::Live(x) << ".<br>";
doc << "x/5 = " << emp::web::Live( [](){ return x/5; } ) << ".<br>";
// Create a button to modify x.
emp::web::Button my_button( [&](){ x+=5; doc.Redraw(); }, "Click me!" );
doc << my_button;
}
Of course, we need to be able to modify Widgets in addition to variables; fortunately this is easy as well.
Can you add a reset button that zeros out x
?
đź”— Finding and Modifying Existing Widgets
There are two ways to keep track of Widgets in Empirical.
One is to simply hold on to a variable associated with the Widget, such as my_button
in our previous examples.
At any point we can still modify something about my_button
.
For example, if we added a line at the end of main:
my_button.SetLabel("PLEASE Click Me!");
We will see that the button label updates to the new string.
The other option we have to keep track of a widget is to specify its HTML identifier so that we can look it up again later.
For example, if when we first declared my_button
we had given it an extra string argument, that string would be used as its identifier.
emp::web::Button my_button(
[](){ x+=5; doc.Redraw(); },
"Click me!",
"my_button"
);
At any point after we insert the button into a container (such as doc
), we are able to request it back from the container again.
So, for example instead of
my_button.SetLabel("PLEASE Click Me!");
we could have said
doc.Button("my_button").SetLabel("PLEASE Click Me!");
and doc
will properly look up the correct button for us (or trip an assert if the required button cannot be found).
In practice, allowing containers to track Widgets is much easier than juggling them all yourself.
Can you figure out how to add an emp::web::TextArea
and use std::cout
to print whatever the user enters to the console in your web browser (which you can view by opening your browser’s developer tools)?
đź”— Controlling CSS
Web page aesthetics are controlled by adjusting the CSS.
You have two options for controlling CSS with Empirical:
- you can do it the traditional way by modifying the HTML file or
- you can control specific CSS settings with the
.CSS
member function associated with all Widgets.
For example, if we wanted our button to be blue with red text, we could add to the end of main the statement
my_button.SetCSS("background-color", "blue").SetCSS("color", "red");
Note the chaining of modifiers.
Many common settings are directly defined as member functions, so the above could also be expressed as
my_button.SetBackground("blue").SetColor("red");
See the documentation for a full list of available functions to modify each Widget type.
đź”— Empirical Tables
Empirical’s tables API differ fundamentally from the HTML API it wraps, preferring a more exact form where the user sets the number of rows and columns to be used.
For example, to build a table with 7 rows and 3 columns, we can declare
emp::web::Table my_table(7, 3, "my_table");
To access a cell from a table, we can simply use the .GetCell(x,y)
member function.
So if we want to fill the table with data, we might do something like
for (size_t r = 0; r < 7; r++) {
for (size_t c = 0; c < 3; c++) {
my_table.GetCell(r,c) << (r+3*c);
}
}
To make the table pretty, we probably want to add some CSS.
my_table.SetCSS("border-collapse", "collapse");
my_table.SetCSS("border", "3px solid");
my_table.CellsCSS("border", "1px solid");
my_table.CellsCSS("padding", "3px");
Note that we are able to target the CSS of all table cells at once.
We can target individual cells using GetCell()
, as well as using
GetRow()
, GetCol()
, GetRowGroup()
, and GetColGroup()
.
Each of these returns a TableWidget
with the appropriate component in focus, so additional modifications are handled correctly.
Finally, of course, make sure to insert the table into the document. Otherwise, it won’t show up!
doc << my_table;
đź”— Canvas
For this section, you’ll need to #include "emp/web/CanvasShape.hpp"
.
Canvas widgets in Empirical wrap HTML canvases, which allow graphics to be drawn on the fly with JavaScript.
To build one, you simply need to create an emp::web::Canvas
object (with the appropriate size) and place it into the document.
For example,
emp::web::Canvas my_canvas(300, 400, "my_canvas");
doc << my_canvas;
would create a 300x400 canvas.
We can then use member functions to draw lines, circles, and rectangles on the Canvas
.
For example,
my_canvas.Circle(100, 100, 40, "red", "black");
would draw a circle at (100,100) with a radius of 40, a face color of red, and an outline of black.
Can you figure out how to draw an emp::web::CanvasRect
?
đź”— Get Animated
For this section, you’ll need to #include "emp/web/Animate.hpp"
.
In this section we’re going to make a fun game where a ball bounces back and forth across the screen with no user controls except Start and Stop. Remember kids, it’s not a Bad Game if it’s actually an Art(istic statement on the Futility of Human Agency).
Remember how JavaScript is event-driven? Under the hood, (AFAIK) it only processes events one at a time. If a callback function for an event never terminates, everything else in the browser window freezes up!
That means in order to run an animation we have to do a fun sort of tango where we let the browser intermittently call a DoFrame
function when it’s time to process the next frame of the animation.
emp::web::Animate
handles these details for us.
We just have to inherit from emp::web::Animate
and override its virtual DoFrame
function.
Here’s the code, with some comments along the way.
main.cpp
:
#include "emp/web/Animate.hpp"
#include "emp/web/web.hpp"
// the div we'll shove stuff into
emp::web::Document doc{"target"};
// inherit from emp::Web::Animate
// and expose inherited methods
class PongAnimator : public emp::web::Animate {
// projectile position and velocity
double x{0};
double dx{10};
// arena width and height
const double width{1000};
const double height{100};
// where we'll draw
emp::web::Canvas canvas{width, height, "canvas"};
public:
PongAnimator() {
// shove canvas into the div
// along with a control button
doc << canvas;
doc << GetToggleButton("Toggle");
}
// overrides base class's virtual function
void DoFrame() override {
canvas.Clear();
canvas.Circle(
x,
height/2,
20,
"blue",
"purple"
);
// bounce
if (std::abs(x - width/2) > width/2) dx = -dx;
// move
x += dx;
}
};
// persists outside scope of main
PongAnimator animator;
int main() { animator.Start(); }
Can you add a “Step” button that calls animator
’s DoFrame
?
đź”— Go, Dog. Go!
Write a quick web app that runs the evolutionary algorithm you coded up in a previous lesson and animates a rudimentary dot plot of average fitness.
We’ll need to refactor our evolve
function into an Evolve
class so that we can easily step one generation at a time instead of running them all at once.
Here’s a head-start on that.
evolve.hpp
:
#pragma once
#include <iostream>
#include "emp/base/vector.hpp"
#include "emp/data/DataFile.hpp"
#include "emp/math/Random.hpp"
#include "emp/math/random_utils.hpp"
#include "fitness.hpp"
#include "selection.hpp"
class Evolve {
const size_t population_size = 50;
size_t curr_gen = 0;
// make random engine
emp::Random rand{-1};
// vector to store our population,
// fill it with randomized genomes between 0 and 1
emp::vector<double> population = emp::RandomDoubleVector(
rand,
population_size,
0.0,
1.0
);
emp::ContainerDataFile<emp::vector<double>> datafile{
emp::MakeContainerDataFile(
std::function<emp::vector<double>()>{
[this](){ return population; }
},
"evo-algo.csv"
)
};
public:
Evolve() {
datafile.AddVar(
curr_gen,
"generation",
"Current Generation"
);
datafile.AddContainerFun(
std::function<double(double)>{[](double x){
return x;
}},
"genome",
"Genome's content"
);
datafile.AddContainerFun(
std::function<double(double)>{[](double x){
return calcFitness(x);
}},
"fitness",
"Genome's Fitness"
);
datafile.PrintHeaderKeys();
datafile.Update();
}
void Step() {
++curr_gen;
emp::vector<double> next_population;
// select individuals for next generation
for (size_t i = 0; i < population_size; ++i) {
double winner = doTournament(
population,
rand,
3
);
next_population.push_back(winner);
}
// do mutation
for (double& ind : next_population) {
if (rand.P(0.25)) {
ind += rand.GetDouble(-1.0, 1.0);
}
}
population = next_population;
datafile.Update();
}
};
Then, start off your source/web.cpp
with something like this.
#include "emp/web/web.hpp"
#include "evo-algo/evolve.hpp"
// global variable...
// won't go out of scope and get destroyed
// once main completes
Evolve evolver;
int main() {
...
Can you also show the live numerical values of best and average fitness? Can you make it look pretty with a little Bootstrap?
The cookie-cut project you instantiated is already set up to help you along!
The command make web
will compile source/web.cpp
for you.
Rember to source
Emscripten before you try to compile with make web
.
Compiling with the make web-debug
command activates debugging features, like additional safety checks and better (human-readable-ish) backtraces on crash.
In order to see debugging output, you’ll need to open your browser’s “developer view.”
In many browsers, this can be accomplished with the shortcut ctrl-shift-i
.
Inside the cookie-cut project, the web products live in web/
so you’ll need to point your web browser to http://localhost:8000/web.
.