Skip to content
This repository

Add :update applet argument for functional equivalent of :draw function. #19

Closed
wants to merge 2 commits into from

5 participants

Phil Hagelberg Sam Aaron Nikita Beloglazov Gary Trakhman Yury Smolsky
Phil Hagelberg

A shot at implementing what we discussed on IRC.

Also needs to accept initial :state as an argument, though it looks
like that's used for other things I don't entirely understand.

Phil Hagelberg technomancy Add :update applet argument for functional equivalent of :draw function.
Also needs to accept initial :state as an argument, though it looks
like that's used for other things I don't entirely understand.
ec6f024
Sam Aaron
Owner

Hey Phil,

looks like a great start. One thing to consider is how this would work with the current state functionality. Perhaps it might make sense to keep it different? It's worth thinking about.

Currently, you can set the state with 'set-state! (which may only be called once to stop it being used as a mutator). This is typically done in the sketch's :setup fn:

(set-state! :foo 1 :bar (atom true))

Once the state has been state, may then be retrieved by calling state and passing the keyword of the state val you'd like to read. i.e. to get the atom associated with the key bar you'd do:

(state :bar)

This would typically be used in the :draw fn.

This means, if you want mutating stuff, you need to store them in refs thus preserving Clojure's nice identity mutation semantics.

With your approach, you probably wouldn't store stuff in refs, rather you'd just keep replacing vals in a similar way to using loop. I therefore wonder whether we need to explicitly store anything with this approach - i.e. it's orthogonal to the 'state' stuff.

Interested in your thoughts...

Phil Hagelberg

Thanks for the clarification. I had the nagging feeling I was missing something re: state.

The approach of working with individual fields and hiding the overall map strikes me as odd; was that something intentional or just something quil inherited from clj-processing? It seems like the current set-state! defn and friends don't get you much over just a top-level atom def in your sketch namespace. But I understand needing to not introduce breaking changes, so I can switch to making :update use its own independent atom.

With your approach, you probably wouldn't store stuff in refs, rather you'd just keep replacing vals in a similar way to using loop

I think of it as being more like reduce since it's the return value that's used. Anyway, the only reason it's accepted as an argument to applet is so that an initial value can be specified; the change always comes from the :update function. Maybe :init would be a better name for that?

Sam Aaron
Owner

What set-state! buys you over a top-level atom def in the sketch namespace is sketch local state i.e. it's not shared across sketches. One of the first traps I fell into when playing with clj-processing was running multiple sketches which shared the same atom and wondering why things weren't working as expected.

:init seems like a good name. My only concern at this stage is to try and not get the two state handling options confused. It's not clear to me which is going to be the most elegant solution and I think it's great to experiment. However, I think we need to be careful to not make things more confusing. Perhaps :seed or :update-seed are other names to consider.

Phil Hagelberg

Yeah, I'd expect to just close over an atom if I wanted sketch-local state though. It's easy enough to do and doesn't require learning anything specific to the quil API. But it does sound like we want something other than the existing :state field, so I've switched the update-fn to use :init instead.

Phil Hagelberg

I feel now that :init might be the wrong thing. Wouldn't it be better to use the return value of the :setup function instead?

Sam Aaron
Owner

Yeah, that definitely makes a lot more sense. I'll look into it when I get a few spare non-baby cycles :-)

Phil Hagelberg

If the draw function can be expressed as a pure function, certain time-based constraints can be implemented as higher-order "quil middlewares", such as looping or a sketch that runs for only N seconds. In addition if even things like the height and width are moved to function arguments, middleware which composes multiple sketches together would be easy to implement.

(Leaving this note to myself as a comment here because I'm more likely to remember it if it's here. But of course if anyone wants to run with the idea before I get a chance please do.)

Sam Aaron
Owner

Sounds extremely sane.

My current plan is to look at major changes like this when I start working on Quil 2.0 which will support Processing 2.0 when it's stable. I think we should leave Quil 1.x.y with the current API for stability purposes.

Any prototypes of ideas would be great to see in the meantime though...

Nikita Beloglazov
Owner

So user can use either update (update and draw something) or only draw (draw something)?

Did you consider (may be in IRC) using update function as only update? So it doesn't have sideeffects like drawing and should only update current state of the world. And draw function only draws this state without modifying it. This way there is a separation between logic and presentation. Every turn first update called and then draw called.

Gary Trakhman

I have a similar idea in a demo implementation, but it's a bit flaky sometimes. I think Processing isn't expecting to be used that way somehow: https://github.com/gtrak/quilltest/blob/master/src/quilltest/core.clj#L35

Nikita Beloglazov
Owner

Functional mode was added in 2.1.0. See wiki article. Thanks for inspiration!

Yury Smolsky

This looks real awesome! It reminds me of Racket's big-bang and a step forward functional style of Clojure.

As a side-note I might suggest to add feature to decouple update function from draw function for example when update function does not need to perform with the same rate as draw. So one would end up with updates per second parameter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 2 unique commits by 1 author.

Apr 18, 2012
Phil Hagelberg technomancy Add :update applet argument for functional equivalent of :draw function.
Also needs to accept initial :state as an argument, though it looks
like that's used for other things I don't entirely understand.
ec6f024
Apr 19, 2012
Phil Hagelberg technomancy Use :init for update functions instead of re-using :state atom. b016821
This page is out of date. Refresh to see the latest.

Showing 1 changed file with 15 additions and 5 deletions. Show diff stats Hide diff stats

  1. +15 5 src/quil/applet.clj
20 src/quil/applet.clj
@@ -156,9 +156,16 @@
156 156
157 157 :setup - a fn to be called once when setting the sketch up.
158 158
159   - :draw - a fn to be repeatedly called at most n times per
160   - second where n is the target frame-rate set for
161   - the visualisation.
  159 + :draw - a fn of no args to be repeatedly called at most n
  160 + times per second where n is the target frame-rate set
  161 + for the visualisation. Cannot be used with :update.
  162 +
  163 + :update - a fn of to be repeatedly called like :draw, but accepting
  164 + both the current frame count and the current :state value.
  165 + The return value of this function will become the new
  166 + :state, much like reduce. Takes precedence over :draw.
  167 +
  168 + :init - Initial value of the state passed to the :update fn.
162 169
163 170 :focus-gained - Called when the sketch gains focus.
164 171
@@ -212,6 +219,7 @@
212 219 (println "Exception in Quil draw-fn for sketch" title ": " e "\nstacktrace: " (with-out-str (print-cause-trace e)))
213 220 (Thread/sleep 1000))))
214 221 draw-fn (if (:safe-draw-fn options) safe-draw-fn draw-fn)
  222 + update-fn (or (:update options) (fn [_ _] (draw-fn)))
215 223 key-pressed-fn (or (:key-pressed options) (fn [] nil))
216 224 key-released-fn (or (:key-released options) (fn [] nil))
217 225 key-typed-fn (or (:key-typed options) (fn [] nil))
@@ -224,7 +232,8 @@
224 232 mouse-clicked-fn (or (:mouse-clicked options) (fn [] nil))
225 233 focus-gained-fn (or (:focus-gained options) (fn [] nil))
226 234 focus-lost-fn (or (:focus-lost options) (fn [] nil))
227   - state (atom nil)
  235 + state (atom (:state options))
  236 + init (atom (:init options))
228 237 target-obj (atom nil)
229 238 prx-obj (proxy [processing.core.PApplet
230 239 clojure.lang.IMeta] []
@@ -312,7 +321,8 @@
312 321 (setup-fn)))
313 322
314 323 (draw
315   - [] (draw-fn)))]
  324 + ([] (swap! init (partial update-fn
  325 + (.frameCount this))))))]
316 326 (applet-run prx-obj title renderer target)
317 327 prx-obj))
318 328

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.