This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-06-11
Channels
- # admin-announcements (9)
- # arachne (1)
- # boot (125)
- # cider (5)
- # clara (34)
- # cljs-dev (11)
- # cljsjs (19)
- # clojure (164)
- # clojure-greece (7)
- # clojure-nl (2)
- # clojure-russia (5)
- # clojure-spec (3)
- # clojurescript (28)
- # clojurex (4)
- # core-async (3)
- # cursive (2)
- # datomic (3)
- # hoplon (268)
- # jobs (4)
- # keechma (2)
- # lambdaisland (1)
- # lein-figwheel (5)
- # leiningen (5)
- # off-topic (3)
- # om (3)
- # onyx (16)
- # re-frame (5)
- # reagent (31)
- # robots (1)
- # spacemacs (3)
- # specter (89)
- # untangled (1)
- # yada (26)
@mhr maybe i misunderstand, but should those reset!
calls happen inside a dosync
?
you are currently asking state to update the page 3 times per undo!
i would think, based on my limited understanding of what you’re trying to do, that you want past, present and future to all have a new value together
it may or may not have an impact on what you’re seeing in performance
but currently, you are moving the system temporarily into a state where items appear in both past, present and future simlutaneously
@thedavidmeister: I'm just learning Clojure, so I'm unfamiliar with dosync
it’s actually part of javelin
so basically
when you do a reset!
then it immediately updates all the cells and the DOM
but, say you had...
(def left (cell :foo))
and (def right (cell nil))
and you rendered the left and right hand sides of your site based on that
you could move something from left to right by doing
(do (reset! left nil) (reset! right :foo))
but...
after the first call to reset!
, :foo
is in neither left or right
so you’ll have nil
in both sides of the page propagate through all the cells, and the DOM
which might be harmless
or might be really bad
but if you (dosync (reset! left nil) (reset! right :foo))
it will do all the resets together, and then propagate the final state of the world together
oh, i have no idea, lol
it completely comes down to what you’re doing in the DOM based on state 😛
i mean, it would have some impact on performance because you’re doing 1/3rd of the work in theory
but i don’t know if “the work” is the bottleneck atm 😉
it's a part of javelin? is this a modified version of the one in the clojure core? https://clojuredocs.org/clojure.core/dosync
because it doesn’t exist in clojurescript
it’s very much like what’s in here under “modelling sock transfers"
what i’m saying is more about helping you avoid subtle bugs or other issues caused by your system being in an inconsistent state from time to time
I was having some conceptual trouble writing it. I had to be careful in my ordering to make sure I wasn't overwriting information I needed in a later reset.
that’s fair, it’s probably good clojure practice 🙂
you could probably do something with a let
too
well clojurescript is based on javascript
so it’s single threaded
and also, the javascript primitives and java primitives are different
so if you (reduce + my-collection)
in clojurescript it will do all the lovely javascript coercion stuff that +
does in javascript
hmm. I made the dosync change and it doesn't seem to have helped performance too much, actually
you might want to do something like (reduce + (map int my-collection))
for example 😉
@mhr have you got this up on github or something?
i could pull it down and have a look
it’s just… you’re assuming the perfomance issue is in the reset!
itself but it could be anywhere really
that's true. I really appreciate you reviewing my code, I'll put it up and put a link here, should take only a minute or so
also, the way you have “past, present, future"
could just be a vector with a “current position” index
and undo would then just be decrementing the index by 1
hahaha it’s really impossible to say when only looking at one part of the system
but you should try 🙂
making things easier to understand and more atomic are good goals in their own rights
even if the performance bottleneck is somewhere else
yes. I'm simulating a vector with an index through a map, it's very convoluted. I'm not sure why I went for that.
@mhr /shrug i guess because you’re learning a bunch of things at the same time?
I remember now. I read http://redux.js.org/docs/recipes/ImplementingUndoHistory.html to see how others implement undo/redo and saw that they were using a map, so I used it without thinking about other approaches.
oh sure
well, there’s also the datomic/datascript approach
where you model the state as a database, and every transaction is applied as a diff
so your undo/redo is just working through diffs
but um, probably don’t worry about that just yet
here’s the link for later though https://github.com/tonsky/datascript
ah cool
yeah it is
it’s definitely simpler
on what scale?
and also, i just don’t think you should optimise for efficiency until you know what is inefficient in context of a specific problem
if your entire application state is a number
[1 2 3]
will be far more efficient than a datascript database
if it’s a complicated structure, then having diffs of the structure will be more efficient than having a vector with a complete representation of every state in it
also, there’s nothing stopping you (actually it’s encouraged) from building your application out of a set of different cells that relate to specific functionality, rather than shoehorning everything into a global “state"
precisely so you can make choices like this based on what you’re doing
ok yeah, i’m seeing the slowdown in your app around the 400 element mark on my laptop
i’ll have a look 🙂
it’s interesting that the performance doesn’t degrade slowly, it very suddenly chokes
so maybe it’s gc or something
the issue is not in the reset @mhr
comment out these lines, i’m well into the thousands with no issues
it’s something to do with reflowing the DOM i would say
if I wanted to render the past and future as list items, though, how would I make that more efficient?
I do want to be able to display undo and redo to the user in some tangible way for the app I'm going to build
well the adding of DOM elements is very low level, it’s just calling javascript functions that add to the DOM with appendChild
let me keep having a play
i think we can safely say that it’s not the updating of the cell itself causing issues
so it’s either something in for-tpl
that we can investigate
or it’s just a limitation of the way browsers add to the DOM
yeah, the main thing with performance between map and the tpl is adding and removing elements
not just adding
specifically when the elements you remove reference a cell that is outside the map/tpl
because the element is never garbage collected
because it is still watching something that exists
so you just end up with a pile of invisible elements in memory that are basically just a memory leak
it's a shame to me that I can't use regular clojure functions, but I see why it's necessary
you can
you can use map for anything you want, but if you create elements that “dial out” to a cell that is not contained to that scope
then… well it’s just how javascript works
it’s doesn’t clean up anything that references other scopes
so the tpl macros just make it so you can automatically re-use those elements
rather than constantly creating new things
but as the issue here is the browser slowing down just from repeated appends
i wouldn’t expect to see much difference between a tpl and a map
anyway, enough talking, i’m going to do some playing around 🙂
@mhr so i whipped up a “best case” scenario using straight js
so that only really slows down from gc on my laptop
so there is something between that quite raw example and what is going on in your example
aaah no
it’s definitely faster
i wouldn’t say it’s perfectly silky
but it doesn’t hit the wall dramatically either
it kind of drifts in and out of being fast
in typical js style
well no
i just ran an experiment using raw js
to see what the absolute best case looks like
for the type of work that you’re doing
which is “append a list item"
my example is completely useless from a “build an application” perspective 😛
it’s literally just incrementing a counter and adding a list item
the counter and the number of list items aren’t even hooked up
i just wanted to see how fast the DOM could move
yeah, maybe...
i think i’ll do some profiling
well that’s the thing
there aren’t really any internals
it’s not like Hiccup where you make a data structure, then render it, then compare the render against the current state and apply the render to the DOM
as soon as you call a HLisp function it appends the child to the DOM
if you have a cell=, that’s just an event handler watching some data
my understanding of HLisp is that it should function very similarly to that gist i posted
pretty sure that’s it
but @micha would know best
hang on, brb
i’m not really an expert on profiling cljs
i’m actually pretty new too 🙂
but i’m just going to have a crack at it with the regular profiler
I've not made any major JS apps either, so I've not used regular JS profiling tools in the browser before either
ooh ok, well there’s total and self
here’s another one
total is the time for that function and all the functions it calls
so… a function might be quite fast and have a big time because something it calls is slow
self is just the time of the function itself
but it’s cumulative tie
i could call a fast function 10k times and it be slower than a slow function i call once
i think that means that the bit that is happening in javelin is fast
and the bit that is happening in jquery is slow
perhaps the developers of javelin should consider switching to raw javascript instead of jquery...
i don’t think javelin does anything with jquery
if you just click on the “inc” button, you can’t really break it
i think that at some point, the “add and update” function becomes slightly slower than the keydown repeat interval on the enter key
so you end up with a backlog of events queued up
i don’t know what the key repeat rate is...
it seems to be about 40ms
based on a very unscientific counting i just did 😛
@mhr i definitely think that maybe someone like @micha might be able to help more with the actual cause of the slowness
but i’ll see if i can help with a throttler
as long as you never go past that 30-40ms threshold you won’t get the runaway queue
so if you just limit the speed at which someone can click to like, 100ms
you should be fine
sleep isn’t really a thing in javascript
either a throttle or a debounce
in your case, you probably want a throttle
i’m just googling to see if someone has made a convenient way to throttle in cljs 🙂
it’s actually very common to run into this kind of issue in js
when you spam many events in quick succession
the examples in that article are resizing the window and scrolling
but holding down a key for 10s+ and responding to every key repeat is another example
i’m actually thinking that it could be cool to include a throttle and debounce in javelin @alandipert @micha ?
it could nicely complement dosync
it sort of speaks to that missile command example you told me about @micha
you could have a dosync
that is time based instead of argument based
or maybe it’s on the cell level...
a cell that you could update as often as you like
but will only propagate its values at most X times per Y time span
@mhr i think this just about hits the extent of my knowledge with javelin/hoplon for what you’re looking at
@mhr i’d suggest you just keep doing what you’re doing, and assume that you can drop in a throttle at some later date if it’s really needed
realistically, even someone typing at 100 wpm is still less than 1 letter per 100ms
it's really late where I am, so for now, I will keep doing what I'm doing until tomorrow when I can look at my code with fresh eyes. I'm almost done converting it to vector + cursor form. it's so much better and cleaner.
sweet 🙂
that's true, but it's not an unexpected use-case that someone would want to spam from their keyboard too
no, but you’re not obligated to reflow the DOM in real time
as long as your underlying state (which we established is easily fast enough to keep up)
has everything they typed
you can throttle how often the DOM responds to that state
@mhr i did this as a quick and dirty experiment
(def state (cell {:past [] :present 0 :future []}))
(def state-t (cell nil))
(defn sync!
[]
(reset! state-t @state)
(with-timeout 100 (sync!)))
(sync!)
(def counter (cell= (:present state-t)))
(def past (cell= (:past state-t)))
(def future (cell= (:future state-t)))
it definitely helped
but again, bottomed out around 1200 elements for me
i’ll see what other people in chat think
i’m 90% sure it’s something in the DOM manipulation and not javelin, but i don’t know why the “raw JS” DOM updates were so much faster than those triggered by Hoplon
@micha: is there a term to describe the indentation style you use? Know of any emacs tools to make it happen magically by any chance?
i add some lispwords
to vi's built-in lisp stuff: lispwords+=page,cell-let,this-as,add-watch,do-watch,elem,alter-var-root,vary-meta,alter-meta!,task-when,specify!,GET,POST
@micha: i mean like the aligning of things in vertical columns... Do you just do that manually?
(ns hoplon.test
(:require
[cljs.test :as t]
[goog.string :as gstring]
[clojure.string :as string]
[hoplon.core :as h :include-macros true]
[javelin.core :as j :include-macros true])
(:import
[goog.string StringBuffer]))
@thedavidmeister: here is a (misnamed) thing to add throttling to a cell: https://gist.github.com/micha/fa95fc3731c29abd71ff