hmm, i kinda of like the idea of combining them
it's less plumbing
and seems like it would be pretty intuitively understood by most
can a function support more than one spec? we could have an alias for sh
with a spec supporting :cb and let the existing sh
barf if you provide :cb with the default spec
i.e., the alias could put sh
into the cb-accepting mode; i.e., select the :cb-accepting spec rather than the regular one
this way i wouldn't have to duplicate the CLJS -> JS -> C plumbing but consumers would still see two different functions
oh, even if that isn't supported, there's an easy workaround:
1 - rename current sh
to sh-private
and mark private so consumers can't touch it.
2 - relax spec on sh-private
to accept :cb. propagate :cb all the way through to C if provided.
3 - create new sh
that calls sh-private
appropriately with original spec disallowing :cb.
4 - create new sh-async
that calls sh-private
appropriate and has spec accepting :cb.
@johanatan: sure, give a combined API a try and see how it feels. We can see if a more complicated spec works out. From a spec perspective it seems that the :fn
part would let us express the relation that the return is nil
if :cb
is present. Return values no longer appear to be checked anyway.
@mfikes: what do you think of the above steps 1-4? seems like best of both worlds to me [shared plumbing but different external functions/specs]
and if you ever wanted to go with combined, it would be as easy as removing sh
and sh-async
and renaming sh-private
to sh
Sounds fine
@johanatan: It may make sense to somehow make the new API fail at runtime indicating that it is not supported if called under 1.x
@johanatan: I'm curious if the value of js/PLANCK_VERSION
can be used to wrap the (defn sh-async ... )
so it only exists in Planck 2.0
(when-not (string/starts-with js/PLANCK_VERSION "1.")
(defn sh-async ,,, ))
Perhaps that may not actually work in the end
Hmm, that should work
defn operates at the global level regardless of where it appears no?
@mfikes: ^
@johanatan: Yes. It is worth trying. I’m wondering if the initialization sequence has js/PLANCK_VERSION
setup early enough.
@mfikes: any idea how to test if something is a function in CLJS? clojure.test/function? doesn't exist
and the output from this isn't very useful without a string compare hack:
cljs.user=> (type (fn [] 9))
#object[Function "function Function() {
[native code]
}"]
fn?
or ifn?
ahh, yep
fn? it is
thx!
What requires did you need for (s/exercise ...) to work?
I got the clojure.spec :as s one
but it's complaining about clojure.test.check.generators now:
cljs.user=> (s/exercise :planck.shell/sh-args)
Var clojure.test.check.generators/simple-type-printable does not exist, clojure.test.check.generators never required
cljs.core/-deref [cljs.core/IDeref] (cljs/spec/impl/gen.cljs:15:3)
You need a copy of test.check
with TCHECK-105 applied. There is a copy of that JAR in this gist: https://gist.github.com/mfikes/4b41b7c406c57228489b5edfb6ffe6a7
It will be normal for it to fail when encountering the need to generate values conforming to #(instance? File %)
.
@mfikes: I’ve pushed some new suff to the socket-pr (as you might have seen)
As far as I can see, I can now implement a web-server using this stuff.
If you have time to take it for a spin, I’d be very happy.
Yes. Perhaps I can give it a shot this weekend.
No stress 🙂
@mfikes: do you happen to know how to unpack a function from a JSObjectRef?
i.e., if i pass in a CLJS function from the CS side, how do I unpack it from the JSObjectRef on the C side
@johanatan: Interestingly, it looks like that whole question was side-stepped with the implementation of setTimeout
@johanatan: If there is no clear way to do it from C, then perhaps you can write some ClojureScript to retain the callback somewhere, and then when the results are available, execute a “script” that says “take this value and apply the callback to it"
(Funky stuff like that was done for function values in Planck’s eval
implementation. See the bottom of http://blog.fikesfarm.com/posts/2016-01-22-clojurescript-eval.html )
link broken?
@johanatan: Fixed
There are probably many ways to skin that cat. Just pointing out that if it is difficult to deal with functions as values, they can be bijectively mapped to numbers so that you deal with numbers on the C side.
Can you point to an example of the "execute a "script" that says "take this value and apply the callback to it"" part of this?
I think the best way to handle it will be to assign a unique ID to each cb on the way in, stash it in a datastructure on the CLJS side, pass the ID into C and then let C callback to the CLJS with that ID and the return value (it's the last leg of that which I need help with).
@mfikes: ^
@johanatan: See https://github.com/mfikes/planck/blob/master/planck/PLKClojureScriptEngine.m#L168
And https://github.com/mfikes/planck/blob/master/planck/PLKClojureScriptEngine.m#L187
I think they share some conceptual similarity
ahh, i see. so you mean generate a literal JS script as string and execute it 🙂
Yeah.. that second snippt
Who knows if all of that is really just a hack. But it has some germ of an idea on how to cobble together something.
It is probably cleaner with most of it on on the ClojureScript side, and the C just calling something on that side with the function ID and the results.
this should work. one risk i have here though: is the JSContext a relatively global concept? i.e., would it change between successive calls to (sh ...) ?
Hmm… I think it is the one and only JSContext that Planck runs.
for the async part, i'm going to have to put a 'wait' on a bkg thread. but when that completes, i still only have the original JSContext (the parent thread/process having returned already)
ya, that's what i figured
so this should work
You may not even need to translate from a the function object to and ID and back again. Perhaps you can opaquely pass it into C and then back out again.
hmm, perhaps but i already went with the ID approach
🙂
this is the translation on the way into C:
(def ^:private sh-async-cbs "sh-async-cbs")
(aset js/window sh-async-cbs (clj->js {}))
(def ^:private cb-idx (atom 0))
(defn assoc-cb [cb]
(let [idx (swap! cb-idx inc)]
(aset js/window sh-async-cbs idx cb)
idx))
so, C will just generate some js like so: function() { window.sh-async-cbs["{ID}"]( res ); }
oh, and remove the function assoc to that ID from window.sh-async-cbs
afterwards
Cool
I like the opaque idea though. Could look something like:
JSEvaluateScript(ctx, "function() { window.do_callback(res); }();", (JSValueRef)opaque_ref, NULL, 0, NULL);
where do_callback
calls its this
with res
better yet: pack both the func to call and the result into an object and specify it for the 'this'
and let do_callback
pull the func and result from its this
[would eliminate having to encode the res
as a string (and the existing impl already encodes it as a JSObject anyway)]
@mfikes: do you think that could work?
there is the complication of not knowing how/when the JSValueRef[] passed into my C function gets cleaned up though.
passing an int from the main thread to the background thread sidesteps that concern/risk
[i.e., it's possible that the JSValueRef which the background thread has been passed from the main thread will not be valid when the result comes back]
@johanatan: I'd try an experiment to see if it works out
kind of a costly experiment though especially since i'm already down the int road quite a ways
seems like someone should know when that JSValueRef[] gets cleaned up too
given C's manual memory management, i'm almost certain that the JSValueRef's are being explicitly freed after the function returns to its caller
otherwise, when would they ever get freed?
I think I will just finish this int approach since I'm confident it will work. And then revisit the other approach after
There was some good coverage of memory management with JavaScriptCore in the WWDC talk about the Objective-C bridge, but I suspect we are off in an area where none of that applies. (Yes integers are hard to leak.)