--- /dev/null
+* 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!
;;; 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))))
: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
(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)
--- /dev/null
+;;; 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
;;; Code:
(require 'cl)
-(require 'find-func)
+(require 'parallel-remote)
(defgroup parallel nil
"Execute stuff in parallel"
(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)
graphical
debug
on-event
+ continue-when-executed
username
hostname
hostport)
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)
(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.
(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)))
(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))
"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