Notes on exporting quil
sketches
Draft of 2019.04.18
May include: programming ↘ Clojure ↗ &c.
I like writing Clojure and ClojureScript for the little sketches I produce, and I have some hopes of doing it more often. When what I’m doing is visual, a lot of the time I like to use quil
, the ClojureScript wrapper around p5.js
.
But the documentation and notes available online are scattered and only approximately findable. So this will be a pile of notes for me to discover, next time. Caveat lector: I work on a Mac, and so I don’t feel the need to help you make your package manager (specifically, your particular crufty old brew
installation) work, when all I really need to do is make mine work, so these notes may not suffice for you.
If you have problems with your package manager—whether it’s homebrew or apt
or whatever, I encourage you to throw it all away and start fresh. That’s actually the point. A good package manager will tell you when something is missing, and will download dependencies for you on the fly. You should be able to read error messages and reconstruct your “absolutely vital” subset of packages, from up-to-date source code that doesn’t have security vulnerabilities; you shouldn’t try to “fix” your old one, just replace it.
Install ClojureScript and ensure quil
works off the shelf. That means:
- make sure homebrew is up to date and working (run
brew doctor
to make absolutely sure) - make sure Clojure (
brew install clojure
works) andleiningen
(brew install leiningen
) are up to date and working lein new quil-cljs myproject
cd myproject
atom .
to launch an editor holding themyproject
project folder- Back in the terminal,
lein figwheel
should successfully compile the (default) demo project that has been constructed, and also launch (1) a web server, (2) thefigwheel
file watcher so that saved changes to [most] files in the project will provoke an immediate recompilation, and most importantly (3) a REPL (but not until you open the project page in a browser). You need the REPL. Yes, most of this stuff can be done by hand, butfigwheel
simplifies most of the work so much that it’s not worth recording the individual tasks here. - navigate (in a web browser) either to the project web page mentioned by
figwheel
when it launched, or open the local file[myproject]/resources/public/index.html
- you should be looking at the demo
quil
project in he browser, with no errors
Change the demo project to do what you want it to do. Whenever you save [most] files, you will trigger an automatic recompilation, and the new version will run in the browser window. Error messages and warnings will also appear in the browser window, in a flash message, so don’t be surprised. Errors will not typically be reported to the REPL.
The demo project uses the defsketch
macro, which will be fine unless you want your quil
code to communicate with the surrounding web page, for instance if you want to launch it with a button, or pass in arguments from the page form elements. It’s very helpful to use the web page [myproject]/resources/public/index.html
not just as a “holder” but to pass in arguments to the sketch, and if that is interesting you won’t easily be able to use the defsketch
macro, which assumes a simple isolation.
The structure I have come to prefer (as of this writing) is something like this:
(ns myproject.core (:require [quil.core :as q :include-macros true] [quil.middleware :as m])) ;; internal functions and sketch-specific definitions here (defn setup [args] ;; stuff happens here {:foo 1 :bar 2} ;; persistent state is this map, returned by this function ) (defn update-state [state] ;; stuff happens here {:foo 2 :bar 3} ;; returns a new state, based on the old one, in each tick ) (defn draw-state [state] ;; invoke quil functions that draw the state in the linked container ) (defn ^:export doMyThing [args] (q/sketch :host "sketch-div-id" ;; note that in quil 3.X, a div not a canvas is used :size [400 400] :setup #(setup args) ;; anon function passing arguments to setup :update update-state :draw draw-state :middleware [m/fun-mode] :features [:no-start] )) (doMyThing args) ;; explicitly launches the loop; may not want this
And in the index.html
... <body> <div id="sketch-div-id"></div> <script src="js/main.js"></script> ... interface here as needed ... <script> function do_the_thing() { var args = // quil args collected from page state here myproject.core.doMyThing(args); } </script> <button onclick="do_the_thing()">Launch!</button> </body>
Note this is only for development. To handle optimized compilation and export, see below
The demo quil
project has two build modes set up in project.clj
. The first, "development"
, is (as of this writing) configured to write non-portable javascript to [myproject]/resources/public/js/main.js
:
{:id "development" :source-paths ["src"] :figwheel true :compiler {:main "myproject.core" :output-to "resources/public/js/main.js" :output-dir "resources/public/js/development" :asset-path "js/development"}}
The other, "optimized"
, is for production of a self-contained script, which will include all dependencies. As it stands in the template version, it will also write to [myproject]/resources/public/js/main.js
. Personally I don’t want that to happen, so I often change it to write instead to .../js/optimized.js
, as shown here.
{:id "optimized" :source-paths ["src"] :compiler {:main "myproject.core" :output-to "resources/public/js/optimized.js" :output-dir "resources/public/js/optimized" :asset-path "js/optimized" :optimizations :advanced}}
In the running REPL (which you launched when starting figwheel
), I will usually invoke (build-once optimized)
, which will compile the code and produce the indicated javascript file.
Note that, if you change the optimized file’s name, as I do, you will also need to change the reference to it in the HTML page to match. Otherwise you will still be loading the development version when the page renders.
That is:
... <body> <div id="sketch-div-id"></div> <script src="js/optimized.js"></script> <!-- change this ^^^^^ to match compiled version --> ... interface here as needed ... <script> function do_the_thing() { var args = // quil args collected from page state here myproject.core.doMyThing(args); } </script> <button onclick="do_the_thing()">Launch!</button> </body>
When you find that suitable, you will find you’ve “exported” the files you need.
However be aware that when you edit project.clj
, the figwheel
watcher will not recompile your project. That means you will need to do a clean build. This is easy enough using the REPL commands listed by figwheel
when it launched, but if you are an impatient sort, you can as easily kill the REPL (ctrl-D
) and relaunch it with lein figwheel
. That’s heavy-handed, but maybe you didn’t see the helpful suggestions figwheel
gave you.
Like me.
To embed the sketch in a separate website, not just the index.html
page provided:
- copy the
optimized.js
file to the appropriate resource directory of the other site; this can be renamed to suit the project - ensure the specified
div
exists on the page where the sketch needs to be located - ensure the specified form elements are present, and are named correctly, if your sketch is at all interactive; if you renamed the javascript file, make sure it’s loaded in the page by name (this can be a problem if you’re cutting-and-pasting code from the
quil
project into the external one).
Not done yet
These notes do not (yet) include two-way interaction; that is, this passes values from the web form elements into the quil
sketch, but not the other way. That will be for another day.
An example would be good about now
ticks: 0
This sketch lets one vary a “selection process” for a simple linear regression, as part of an explanation I’m writing about something else. The details of the wiggling lines aren’t important, just the fact that changing the radio button selection and clicking “restart” will affect the sketch in the appropriate way.
I developed the interface in the quil
development environment, obviously avoiding CSS tweaks and stuff that I knew would be provided by the eventual project here. I did rename the javascript file from optimized.js
to pareto-fit.js
in this site’s source tree, simply because I have several dozen such files to juggle, and often want multiples in a single page.
The radio set (note the shared name
field, so all the buttons act as a single set in HTML5) loads a simple “value” string into the Javascript variable var m = document.querySelector('input[name="mode"]:checked').value;
. That string is passed into the quil
function paretofit.core.shimmy(m);
intact, and is parsed there to affect the behavior of the sketch.
Note also that any ClojureScript function intended to be called from the surrounding page will need to be labeled with the ^:export
metadata flag. Otherwise, the optimized code will rename the function internally (to compress the resulting file), and you won’t be able to call it at all.
Arguably, this is true also for reporting functions your code might expose, for instance if you wanted to display (on the page) a “best value found so far” or a count of ticks executed in the quil
sketch on the web page.
As you can see, the example I’ve included does in fact actively rewrite the text on the web page as it runs, by replacing the “ticks” value in the DOM. That’s not at all complicated, but it does introduce some extra work to ensure consistency and to avoid some easy-to-make mistakes (like not having the DOM element to write the value into). I’ll deal with that part later.
Alternately, you may find versions of this same kind of interactive dynamics using a ClojureScript atom
in various ways. Maybe we can do that too… next time.