Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a docs/ site for Blink! #168

Merged
merged 8 commits into from
Oct 25, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
104 changes: 104 additions & 0 deletions docs/Manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "ff2595695fc4f14427358ce2593f867085c45dcb"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "1.2.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["LinearAlgebra", "Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[DocStringExtensions]]
deps = ["LibGit2", "Markdown", "Pkg", "Test"]
git-tree-sha1 = "a016e0bfe98a748c4488e2248c2ef4c67d6fdd35"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.5.0"

[[Documenter]]
deps = ["Compat", "DocStringExtensions", "Logging", "REPL"]
git-tree-sha1 = "7534ff3c50333a69ae0778d4d869e6f5c9ec022a"
uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
version = "0.19.6"

[[InteractiveUtils]]
deps = ["LinearAlgebra", "Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[LibGit2]]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[Pkg]]
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[Test]]
deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[UUIDs]]
deps = ["Random"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
2 changes: 2 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
21 changes: 21 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Documenter, Blink

makedocs(
modules = [Blink],
format = :html,
sitename = "Blink",
pages = [
"index.md",
"guide.md",
"Communication" => "communication.md",
"api.md",
#"Subsection" => [
# ...
#]
],
)

#deploydocs(
# repo = "github.com/NHDaly/Blink.jl.git",
# julia = "1.0"
#)
30 changes: 30 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# API

## Window

```@docs
Window
```

```@docs
title
progress
flashframe
```

### Misc

```@docs
opentools
closetools
tools
```

## RPC

```@docs
@js
@js_
js
Blink.JSString
```
202 changes: 202 additions & 0 deletions docs/src/communication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# [Communication between Julia and Javascript](@id Communication)

After creating a Window and loading HTML and JS, you may want to interact with
julia code (e.g. by clicking a button in HTML, or displaying a plot from julia).

This section covers this two-way communication.

## Julia to Javascript
```@setup Blink-win
using Blink
win = Window(Dict(:show=>false), async=false)
```

The easiest way to communicate to javascript from julia is with the [`@js`](@ref) and
[`@js_`](@ref) macros. These macros allow you to execute arbitrary javascript code in a
given Window.

```@repl Blink-win
@js win x = 5;
@js win x
```

The `@js_` macro executes its code asynchronously, but doesn't return its
result:
```@repl Blink-win
@time @js win begin # Blocks until finished; `i` is returned
for i in 0:1000000 end # waste time
i # return i
end

@time @js_ win begin # Returns immediately, but `i` is not returned.
for i in 0:1000000 end # waste time
i # This is ignored
end
```

If your javascript expression is complex, or you want to copy-paste existing
javascript, it can be easier to represent it as a pure javascript string.
For that, you can call the [`js`](@ref) function with a [`JSString`](@ref Blink.JSString):
```@repl Blink-win
body!(win, """<div id="box" style="color:red;"></div>""", async=false);
div_id = "box";
js(win, Blink.JSString("""document.getElementById("$div_id").style.color"""))
```

Note that the code passed to these macros runs in its own scope, so any
javascript variables you create with `var` (or the `@var` equivalent for `@js`)
will be inaccessible after returning:
```@repl Blink-win
@js win (@var x_var = 5; x_var) # x_var is only accessible within this scope.
@js win x_var
```

## Javascript to Julia
Communication from javascript to julia currently works via a message passing
interface.

To invoke julia code from javascript, you specify a julia callback via `handle`:
```julia-repl
julia> handle(w, "press") do args
@show args
end
```
This callback can then be triggered from javscript via `Blink.msg()`:
```@setup handler
using Blink
w = Window(Dict(:show=>false), async=false)
handle(w, "press") do args
@show args
end
```
```@repl handler
@js w Blink.msg("press", "Hello from JS");
```
Note that `Blink.msg` takes _exactly_ 1 argument. To pass more or fewer,
consider passing an array:
```@repl handler
@js w Blink.msg("press", [1,2,3]);
```
(TODO: is this on purpose? Can we fix this to allow variadic args?)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this on purpose? Maybe I should start a separate issue about it, but it seems weird that this is the API. Could the javascript function be changed to be variadic? (I actually don't know if you can do that in javascript..)


Finally, here is an example that uses a button to call back to julia:
```@setup Blink-w
using Blink
w = Window(Dict(:show=>false), async=false)
```
```@repl Blink-w
handle(w, "press") do arg
println(arg)
end
body!(w, """<button onclick='Blink.msg("press", "HELLO")'>go</button>""", async=false);
```
Now, clicking the button will print `HELLO` to julia's STDOUT.


## Back-and-forth

Note that you cannot make a synchronous call to javascript from _within_ a julia
callback, or you'll cause julia to hang:

**BAD**:
```julia-repl
julia> @js w x = 5

julia> handle(w, "press") do args...
# Increment x and get its new value
x = @js w (x += 1; x) # ERROR: Cannot make synchronous calls within a callback.
println("New value: $x")
end
#9 (generic function with 1 method)

julia> @js w Blink.msg("press", [])

# JULIA HANGS UNTIL CTRL-C, WHICH KILLS YOUR BLINK WINDOW.
```

**GOOD**: Instead, if you need to access the value of `x`, you should simply
provide it when invoking the `press` handler:
```@repl Blink-w
@js w x = 5

handle(w, "press") do args...
x = args[1]
# Increment x
@js_ w (x = $x + 1) # Note the _asynchronous_ call.
println("New value: $x")
end

@js w Blink.msg("press", x)
# JULIA HANGS UNTIL CTRL-C, WHICH KILLS YOUR BLINK WINDOW.
```


## Tasks

The julia webserver is implemented via Julia
[Tasks](https://docs.julialang.org/en/v1/manual/control-flow/#man-tasks-1). This
means that julia code invoked from javascript will run _sort of_ in parallel to
your main julia code.

In particular:
- Tasks are _coroutines, not threads_, so they aren't truly running in parallel.
- Instead, execution can switch between your code and the coroutine's code whenever a piece of computation is _interruptible_.

So, if your Blink callback handler performs uninterruptible work, it will fully
occupy your CPU, preventing any other computation from occuring, and can
potentially hang your computation.

### Examples:

**BAD**: If your callback runs a long loop, it won't be uninterruptible while
it's running:
```julia-repl
julia> handle(w, "press") do args...
println("Start")
while true end # infinite loop
println("End")
end
#40 (generic function with 1 method)

julia> body!(w, """<button onclick='Blink.msg("press", 1)'>go</button>""", async=false);

julia> # CLICK THE go BUTTON, AND YOUR PROCESS WILL FREEZE
Start
```

**BAD**: The same is true if your _main_ julia computation is hogging the CPU, then
your callback can't run:
```julia-repl
julia> handle(w, "press") do args...
println("Start")
sleep(5) # This will happily yield to any other computation.
println("End")
end
#41 (generic function with 1 method)

julia> body!(w, """<button onclick='Blink.msg("press", 1)'>go</button>""", async=false);

julia> while true end # Infinite loop

# NOW, CLICK THE go BUTTON, AND NOTHING HAPPENS, SINCE THE CPU IS BEING HOGGED!
```

**GOOD**: So to allow for happy communication, all your computations should be interruptible, which you can achieve with calls such as `yield`, or `sleep`:
```julia-repl
julia> handle(w, "press") do args...
println("Start")
sleep(5) # This will happily yield to any other computation.
println("End")
end
#39 (generic function with 1 method)

julia> body!(w, """<button onclick='Blink.msg("press", 1)'>go</button>""", async=false);

julia> while true # Still an infinite loop, but a _fair_ one.
yield() # This will yield to any other computation, allowing the callback to run.
end

# NOW, CLICKING THE go BUTTON WILL WORK CORRECTLY ✅
Start
End
```
Loading