Update Emacs Parallel
This commit is contained in:
parent
c9215889cc
commit
468b183880
4 changed files with 260 additions and 27 deletions
147
lisp/emacs-parallel/README.org
Normal file
147
lisp/emacs-parallel/README.org
Normal file
|
@ -0,0 +1,147 @@
|
|||
* Emacs Parallel
|
||||
|
||||
Emacs Parallel is yet another library to simulate parallel
|
||||
computations in Emacs (because it lacks threads support in Elisp).
|
||||
|
||||
* STARTED HowTo
|
||||
|
||||
You can execute a simple function a retrive the result like this:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(parallel-get-result (parallel-start (lambda () (* 42 42))))
|
||||
⇒ 1764
|
||||
#+END_SRC
|
||||
|
||||
Though you won't benefit from the parallelism because
|
||||
~parallel-get-result~ is blocking, that is it waits for the function
|
||||
to be executed.
|
||||
|
||||
So you can use define a callback to be called when the function is
|
||||
finished:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(parallel-start (lambda () (sleep-for 4.2) "Hello World")
|
||||
:post-exec (lambda (results _status)
|
||||
(message (first results))))
|
||||
⊣ Hello World
|
||||
#+END_SRC
|
||||
|
||||
Here, why ~(first results)~ and not ~result~? Because you can send
|
||||
data from the remote instance while it's running with
|
||||
~parallel-remote-send~:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(parallel-start (lambda ()
|
||||
(parallel-remote-send "Hello")
|
||||
(sleep-for 4.2)
|
||||
"World")
|
||||
:post-exec (lambda (results _status)
|
||||
(message "%s"
|
||||
(mapconcat #'identity (reverse results) " "))))
|
||||
⊣ Hello World
|
||||
#+END_SRC
|
||||
As you may have noticed the results are pushed in a list, so the
|
||||
first element is the result returned by the function called, the
|
||||
second is the last piece of data send, and so on...
|
||||
|
||||
And of course you can execute some code when you receive data from
|
||||
the remote instance:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(parallel-start (lambda ()
|
||||
(parallel-remote-send 42)
|
||||
(sleep-for 4.2) ; heavy computation to compute PI
|
||||
pi)
|
||||
:on-event (lambda (data)
|
||||
(message "Received %S" data)))
|
||||
⊣ Received 42
|
||||
⊣ Received 3.141592653589793
|
||||
#+END_SRC
|
||||
|
||||
Because the function is executed in another Emacs instance (in Batch
|
||||
Mode by default), the environment isn't the same. However you can
|
||||
send some data with the ~env~ parameter:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(let ((a 42)
|
||||
(b 12))
|
||||
(parallel-get-result (parallel-start (lambda (a b) (+ a b))
|
||||
:env (list a b))))
|
||||
⇒ 54
|
||||
#+END_SRC
|
||||
|
||||
By default, the remote Emacs instance is exited when the function is
|
||||
executed, but you can keep it running with the
|
||||
~:continue-when-executed~ option and send new code to be executed
|
||||
with ~parellel-send~.
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(let ((task (parallel-start (lambda () 42)
|
||||
:continue-when-executed t)))
|
||||
(sleep-for 4.2)
|
||||
(parallel-send task (lambda () (setq parallel-continue-when-executed nil) 12))
|
||||
(parallel-get-results task))
|
||||
⇒ (12 42)
|
||||
#+END_SRC
|
||||
|
||||
As you can see, to stop the remote instance you have to set the
|
||||
variable ~parallel-continue-when-executed~ to nil.
|
||||
|
||||
* Modules
|
||||
|
||||
** Parallel XWidget
|
||||
|
||||
[[http://www.emacswiki.org/emacs/EmacsXWidgets][Emacs XWidget]] is an experimental branch which permits to embed GTK+
|
||||
widget inside Emacs buffers. For instance, it is possible to use it
|
||||
to render an HTML page using the webkit engine within an Emacs
|
||||
buffer.
|
||||
|
||||
With this module, you can configure your "main" Emacs to use
|
||||
another one to render web pages.
|
||||
|
||||
Let's assume that you've cloned [[https://github.com/jave/xwidget-emacs][the Emacs XWidget repository]] in
|
||||
~$HOME/src/emacs-xwidget/~. Once you've compiled it, an Emacs
|
||||
executable is available ~$HOME/src/emacs-xwidget/src/emacs~.
|
||||
|
||||
Configure ~parallel-xwidget~ to use it:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(setq parallel-xwidget-config (list :emacs-path
|
||||
(concat (getenv "HOME")
|
||||
"/src/emacs-xwidget/src/emacs")))
|
||||
#+END_SRC
|
||||
|
||||
Then configure your current Emacs to use it:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(setq browse-url-browser-function 'parallel-xwidget-browse-url)
|
||||
#+END_SRC
|
||||
|
||||
You can check it out with M-x browse-url RET google.com RET.
|
||||
|
||||
* Tips & Tricks
|
||||
|
||||
If your windows manager is smart enough (like StumpwWM) you can use
|
||||
it to move graphical windows (Emacs frames) in another desktop.
|
||||
|
||||
For example, I use this to move Emacs frames (with the title
|
||||
"emacs-debug") to the group (aka desktop) 9:
|
||||
#+BEGIN_SRC lisp
|
||||
(define-frame-preference "9"
|
||||
(0 nil t :title "emacs-debug"))
|
||||
#+END_SRC
|
||||
|
||||
And this to specify the title of the frame:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(parallel-start (lambda () 42)
|
||||
:no-batch t
|
||||
:emacs-args '("-T" "emacs-debug"))
|
||||
#+END_SRC
|
||||
|
||||
* TODO How does it work?
|
||||
|
||||
* Known limitations
|
||||
|
||||
You can only send data to the remote (with the ~env~ parameter) or
|
||||
from the remote (with ~parallel-send~ and ~parallel-remote-send~)
|
||||
that have a printed representation (see [[info:elisp#Printed%20Representation][info:elisp#Printed
|
||||
Representation]]).
|
||||
|
||||
So you can pass around numbers, symbols, strings, lists, vectors,
|
||||
hash-table but you can't pass buffers, windows, frames...
|
||||
|
||||
|
||||
It lacks documentation, tests and probably a clean API, but I'm
|
||||
working on it!
|
|
@ -22,12 +22,15 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl)
|
||||
|
||||
(defvar parallel-service nil)
|
||||
(defvar parallel-task-id nil)
|
||||
(defvar parallel-client nil)
|
||||
(defvar parallel--executed nil)
|
||||
(defvar parallel-continue-when-executed nil)
|
||||
|
||||
(defun parallel-send (data)
|
||||
(defun parallel-remote-send (data)
|
||||
(process-send-string parallel-client
|
||||
(format "%S " (cons parallel-task-id data))))
|
||||
|
||||
|
@ -39,7 +42,7 @@
|
|||
:host "localhost"
|
||||
:family 'ipv4))
|
||||
(set-process-filter parallel-client #'parallel-remote--filter)
|
||||
(parallel-send 'code)
|
||||
(parallel-remote-send 'code)
|
||||
(when noninteractive ; Batch Mode
|
||||
;; The evaluation is done in the `parallel--filter' but in Batch
|
||||
;; Mode, Emacs doesn't wait for the input, it stops as soon as
|
||||
|
@ -48,15 +51,30 @@
|
|||
(sleep-for 10)))) ; arbitrary chosen
|
||||
|
||||
(defun parallel-remote--filter (_proc output)
|
||||
(parallel-send
|
||||
(if (or noninteractive
|
||||
(not debug-on-error))
|
||||
(condition-case err
|
||||
(eval (read output))
|
||||
(error err))
|
||||
(eval (read output))))
|
||||
(setq parallel--executed t)
|
||||
(kill-emacs))
|
||||
(dolist (code (parallel--read-output output))
|
||||
(parallel-remote-send
|
||||
(if (or noninteractive
|
||||
(not debug-on-error))
|
||||
(condition-case err
|
||||
(eval code)
|
||||
(error err))
|
||||
(eval code))))
|
||||
(unless parallel-continue-when-executed
|
||||
(setq parallel--executed t)
|
||||
(kill-emacs)))
|
||||
|
||||
(defun parallel--read-output (output)
|
||||
"Read lisp forms from output and return them as a list."
|
||||
(loop with output = (replace-regexp-in-string
|
||||
"\\`[ \t\n]*" ""
|
||||
(replace-regexp-in-string "[ \t\n]*\\'" "" output)) ; trim string
|
||||
with start = 0
|
||||
with end = (length output)
|
||||
for ret = (read-from-string output start end)
|
||||
for data = (first ret)
|
||||
do (setq start (rest ret))
|
||||
collect data
|
||||
until (= start end)))
|
||||
|
||||
(provide 'parallel-remote)
|
||||
|
||||
|
|
59
lisp/emacs-parallel/parallel-xwidget.el
Normal file
59
lisp/emacs-parallel/parallel-xwidget.el
Normal file
|
@ -0,0 +1,59 @@
|
|||
;;; parallel-xwidget.el ---
|
||||
|
||||
;; Copyright (C) 2013 Grégoire Jadi
|
||||
|
||||
;; Author: Grégoire Jadi <gregoire.jadi@gmail.com>
|
||||
|
||||
;; This program is free software: you can redistribute it and/or
|
||||
;; modify it under the terms of the GNU General Public License as
|
||||
;; published by the Free Software Foundation, either version 3 of
|
||||
;; the License, or (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'parallel)
|
||||
(require 'browse-url)
|
||||
|
||||
(defgroup parallel-xwidget nil
|
||||
"Browse the web in another emacs instance with XWidget."
|
||||
:group 'emacs)
|
||||
|
||||
(defvar parallel-xwidget--task nil)
|
||||
|
||||
(defcustom parallel-xwidget-config nil
|
||||
"Parallel configuration."
|
||||
:type 'alist
|
||||
:group 'parallel-xwidget)
|
||||
|
||||
(defun parallel-xwidget--init ()
|
||||
(setq parallel-xwidget--task
|
||||
(parallel-start (lambda ()
|
||||
(require 'xwidget))
|
||||
:graphical t
|
||||
:continue-when-executed t
|
||||
:config parallel-xwidget-config)))
|
||||
|
||||
(defun parallel-xwidget-browse-url (url &optional new-session)
|
||||
"Browse URL in another Emacs instance."
|
||||
(interactive (browse-url-interactive-arg "xwidget-webkit URL: "))
|
||||
(unless (and parallel-xwidget--task
|
||||
(eq 'run (parallel-status parallel-xwidget--task)))
|
||||
(parallel-xwidget--init))
|
||||
(parallel-send parallel-xwidget--task
|
||||
(lambda (url new-session)
|
||||
(xwidget-webkit-browse-url url new-session))
|
||||
(url-tidy url) new-session))
|
||||
|
||||
(provide 'parallel-xwidget)
|
||||
|
||||
;;; parallel-xwidget.el ends here
|
|
@ -23,7 +23,7 @@
|
|||
;;; Code:
|
||||
|
||||
(require 'cl)
|
||||
(require 'find-func)
|
||||
(require 'parallel-remote)
|
||||
|
||||
(defgroup parallel nil
|
||||
"Execute stuff in parallel"
|
||||
|
@ -87,7 +87,7 @@
|
|||
|
||||
(defun* parallel-start (exec-fun &key post-exec env timeout
|
||||
emacs-path library-path emacs-args
|
||||
graphical debug on-event
|
||||
graphical debug on-event continue-when-executed
|
||||
username hostname hostport
|
||||
config)
|
||||
(parallel--init-server)
|
||||
|
@ -101,6 +101,7 @@
|
|||
graphical
|
||||
debug
|
||||
on-event
|
||||
continue-when-executed
|
||||
username
|
||||
hostname
|
||||
hostport)
|
||||
|
@ -113,7 +114,7 @@
|
|||
library-path (or library-path
|
||||
(plist-get config :library-path)
|
||||
(plist-get parallel-config :library-path)
|
||||
(find-library-name "parallel-remote")))
|
||||
(locate-library "parallel-remote")))
|
||||
|
||||
(let ((task (parallel--new-task))
|
||||
proc tunnel ssh-args)
|
||||
|
@ -127,6 +128,7 @@
|
|||
(put task 'on-event on-event))
|
||||
(put task 'results nil)
|
||||
(put task 'status 'run)
|
||||
(put task 'queue nil)
|
||||
|
||||
;; We need to get the tunnel if it exists so we can send the right
|
||||
;; `service' to the remote.
|
||||
|
@ -150,6 +152,7 @@
|
|||
(process-contact parallel--server :service)))
|
||||
"--eval" (format "(setq parallel-task-id '%S)" task)
|
||||
"--eval" (format "(setq debug-on-error '%S)" debug)
|
||||
"--eval" (format "(setq parallel-continue-when-executed '%S)" continue-when-executed)
|
||||
"-f" "parallel-remote--init"
|
||||
emacs-args)))
|
||||
|
||||
|
@ -239,23 +242,20 @@ to `funcall' FUN with ENV as arguments."
|
|||
(defun parallel--filter (connection output)
|
||||
"Server filter used to retrieve the results send by the remote
|
||||
process and send the code to be executed by it."
|
||||
(loop with output = (replace-regexp-in-string
|
||||
"\\`[ \t\n]*" ""
|
||||
(replace-regexp-in-string "[ \t\n]*\\'" "" output)) ; trim string
|
||||
with start = 0
|
||||
with end = (length output)
|
||||
for ret = (read-from-string output start end)
|
||||
for data = (first ret)
|
||||
do (setq start (rest ret))
|
||||
do (parallel--process-output connection (first data) (rest data))
|
||||
until (= start end)))
|
||||
(dolist (data (parallel--read-output output))
|
||||
(parallel--process-output connection (first data) (rest data))))
|
||||
|
||||
(defun parallel--process-output (connection task result)
|
||||
(put task 'connection connection)
|
||||
(cond ((and (not (get task 'initialized))
|
||||
(eq result 'code))
|
||||
(process-send-string connection
|
||||
(parallel--call-with-env (get task 'exec-fun)
|
||||
(get task 'env)))
|
||||
(apply #'parallel-send
|
||||
task
|
||||
(get task 'exec-fun)
|
||||
(get task 'env))
|
||||
(let ((code nil))
|
||||
(while (setq code (pop (get task 'queue)))
|
||||
(apply #'parallel-send task (car code) (cdr code))))
|
||||
(put task 'initialized t))
|
||||
(t
|
||||
(push result (get task 'results))
|
||||
|
@ -296,6 +296,15 @@ result returned by exec-fun."
|
|||
"Stop TASK."
|
||||
(delete-process (get task 'proc)))
|
||||
|
||||
(defun parallel-send (task fun &rest env)
|
||||
"Send FUN to be evaluated by TASK in ENV."
|
||||
(let ((connection (get task 'connection)))
|
||||
(if connection
|
||||
(process-send-string
|
||||
connection
|
||||
(parallel--call-with-env fun env))
|
||||
(push (cons fun env) (get task 'queue)))))
|
||||
|
||||
(provide 'parallel)
|
||||
|
||||
;;; parallel.el ends here
|
||||
|
|
Loading…
Add table
Reference in a new issue