Created
April 5, 2017 06:03
-
-
Save nbeloglazov/0828efd3b674bdf587e4a12f83729432 to your computer and use it in GitHub Desktop.
Example of using long-running background tasks with Quil. Quil's UI thread only displays data while background task perform CPU-intensive work (multiplying matrices) without blocking UI thread.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns longtask.core | |
(:require [quil.core :as q] | |
[quil.middleware :as m])) | |
; This program shows an example how to integrate long-running | |
; background tasks into Quil sketch. Clicking on the left or | |
; right half of the screen starts a new background task which does following: | |
; 1. Generates random square matrix of size rand of [100, 200, 500, 1000] | |
; 2. Multiplies matrix by itself. During the multiplication background thread | |
; updates (:progress state) value so Quil can display the progress. | |
; 3. Once the multiplication is done thread updates (:messages state) | |
; list to append "finished" message. | |
; | |
; This example supports 2 parallel background tasks at the same time. | |
; State object used by communicate data from background tasks to | |
; Quil :draw function. Tasks update :progress and :messages fields | |
; while :draw function prints them in sketch. | |
(def state | |
(atom | |
{:progress [0 0] | |
:messages [[] []] | |
:futures [(future) (future)] | |
:highlighted nil})) | |
(defn rand-matrix | |
"Generates matrix NxN with random values from 0 to 100." | |
[n] | |
(let [rand-row #(repeatedly n (partial rand-int 100))] | |
(repeatedly n rand-row))) | |
(defn scalar-mult [a b] | |
(apply + (map * a b))) | |
(defn transpose [m] | |
(apply mapv vector m)) | |
(defn update-progress [ind size row] | |
(let [progress (quot (* 100 row) size)] | |
(swap! state assoc-in [:progress ind] progress))) | |
(defn append-message [ind message] | |
(swap! state update-in [:messages ind] #(conj % message))) | |
(defn matrix-squared | |
"Multiplies matrix A by B. ind is index of state to update. | |
This function doesn't return anything so result matrix is | |
lost. The only purpose of this function is to run some | |
CPU-intensive computation and update progress." | |
[a ind] | |
(let [n (count a) | |
transp-a (doall (transpose a))] | |
(dotimes [r n] | |
(update-progress ind n r) | |
(dotimes [c n] | |
(scalar-mult (nth a r) (nth transp-a c)))))) | |
(defn run-task | |
"Runs task of calculating square of a random matrix." | |
[ind] | |
(let [n (rand-nth [100 200 500 1000])] | |
(append-message ind (str "starting, size " n)) | |
(matrix-squared (rand-matrix n) ind) | |
(append-message ind (str "finished")))) | |
(defn launch-task | |
"Launches task of calculating square of a random matrix. | |
This function starts task in a new thread." | |
[ind] | |
(let [prev-task (get-in @state [:futures ind])] | |
(if (future-done? prev-task) | |
(let [new-task (future (run-task ind))] | |
(swap! state assoc-in [:futures ind] new-task)) | |
(append-message ind "previous task still running")))) | |
(defn draw [] | |
(let [{:keys [progress messages highlighted]} @state] | |
(q/background 240) | |
; Split screen into 2 halves, left and right. | |
; Each half is going to display results of its task. | |
(doseq [ind [0 1] | |
:let [progress (nth progress ind) | |
messages (reverse (nth messages ind))]] | |
(q/with-translation [(* ind (/ (q/width) 2)) 0] | |
; Highglight half of the screen where cursors hovering | |
(when (= highlighted ind) | |
(q/fill 255 253 208) | |
(q/rect 0 0 (/ (q/width) 2) (q/height))) | |
; Print progress of the current task and all its | |
; messages | |
(q/fill 0) | |
(q/text (str "Progress #1: " progress "%") 10 20) | |
(dotimes [i (count messages)] | |
(q/text (nth messages i) 10 (+ 40 (* i 15)))))))) | |
(defn half-under-mouse | |
"Returns 0 if mouse is over the left half of the sketch | |
end and 1 if it's over the right half." | |
[] | |
(if (< (q/mouse-x) (/ (q/width) 2)) 0 1)) | |
(defn mouse-moved [] | |
(swap! state assoc :highlighted (half-under-mouse))) | |
(defn mouse-clicked [] | |
(launch-task (half-under-mouse))) | |
(q/defsketch longtask | |
:title "You spin my circle right round" | |
:size [500 500] | |
:draw draw | |
:features [:keep-on-top] | |
:mouse-moved mouse-moved | |
:mouse-clicked mouse-clicked | |
:middleware [m/pause-on-error]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment