]> git.eshelyaron.com Git - emacs.git/commitdiff
Update Emacs Parallel
authorGrégoire Jadi <gregoire.jadi@gmail.com>
Mon, 19 Aug 2013 18:46:52 +0000 (20:46 +0200)
committerGrégoire Jadi <gregoire.jadi@gmail.com>
Mon, 19 Aug 2013 18:46:52 +0000 (20:46 +0200)
lisp/emacs-parallel/README.org [new file with mode: 0644]
lisp/emacs-parallel/parallel-remote.el
lisp/emacs-parallel/parallel-xwidget.el [new file with mode: 0644]
lisp/emacs-parallel/parallel.el

diff --git a/lisp/emacs-parallel/README.org b/lisp/emacs-parallel/README.org
new file mode 100644 (file)
index 0000000..7430505
--- /dev/null
@@ -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!
index 5c24e55e089b2aebd615af2b5fcd8e4ad251620a..54626afc2677546dad271eae53721ac482c373b1 100644 (file)
 
 ;;; 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
       (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)
 
diff --git a/lisp/emacs-parallel/parallel-xwidget.el b/lisp/emacs-parallel/parallel-xwidget.el
new file mode 100644 (file)
index 0000000..7e23863
--- /dev/null
@@ -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
index 89655af3ee845fd7a751adb1b9cf830bd7659c45..3e5eccfd73c0157589beb3b0b90be586663ea093 100644 (file)
@@ -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)
                          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)))
 
@@ -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