From: Grégoire Jadi Date: Mon, 19 Aug 2013 18:46:52 +0000 (+0200) Subject: Update Emacs Parallel X-Git-Tag: emacs-25.0.90~2769^2~1 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=468b1838805e9c09c6a4297e583d3cde37c9518b;p=emacs.git Update Emacs Parallel --- diff --git a/lisp/emacs-parallel/README.org b/lisp/emacs-parallel/README.org new file mode 100644 index 00000000000..743050518be --- /dev/null +++ b/lisp/emacs-parallel/README.org @@ -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! diff --git a/lisp/emacs-parallel/parallel-remote.el b/lisp/emacs-parallel/parallel-remote.el index 5c24e55e089..54626afc267 100644 --- a/lisp/emacs-parallel/parallel-remote.el +++ b/lisp/emacs-parallel/parallel-remote.el @@ -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) diff --git a/lisp/emacs-parallel/parallel-xwidget.el b/lisp/emacs-parallel/parallel-xwidget.el new file mode 100644 index 00000000000..7e23863d6eb --- /dev/null +++ b/lisp/emacs-parallel/parallel-xwidget.el @@ -0,0 +1,59 @@ +;;; parallel-xwidget.el --- + +;; Copyright (C) 2013 Grégoire Jadi + +;; Author: Grégoire Jadi + +;; 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 . + +;;; 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 diff --git a/lisp/emacs-parallel/parallel.el b/lisp/emacs-parallel/parallel.el index 89655af3ee8..3e5eccfd73c 100644 --- a/lisp/emacs-parallel/parallel.el +++ b/lisp/emacs-parallel/parallel.el @@ -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