1## Copyright (C) 2015-2020 Olaf Till <i7tiol@t-online.de>
2##
3## This program is free software; you can redistribute it and/or modify it under
4## the terms of the GNU General Public License as published by the Free Software
5## Foundation; either version 3 of the License, or (at your option) any later
6## version.
7##
8## This program is distributed in the hope that it will be useful, but WITHOUT
9## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
11## details.
12##
13## You should have received a copy of the GNU General Public License along with
14## this program; if not, see <http://www.gnu.org/licenses/>.
15
16## -*- texinfo -*-
17## @deftypefn{Function File} {} rfeval (@var{func}, @dots{}, @var{nout}, @var{isout}, @var{connection})
18## Evaluate a function at a remote machine.
19##
20## @var{func} is evaluated with arguments @code{@dots{}} and number of
21## output arguments set to @var{nout} at remote machine given by
22## @var{connection}. If @var{isout} is not empty, it must be a logical
23## array with @var{nout} elements, which are true for each of the
24## @var{nout} output arguments which are requested from the function;
25## the other output arguments will be  marked as not requested
26## with @code{~} at remote execution.
27##
28## This function can only be successfully called at the client machine.
29## See @code{pconnect} for a description of the @var{connection}
30## variable. @var{connection} must contain one single connection.
31##
32## If an output argument is given to @code{rfeval}, the function waits
33## for completion of the remote function call, retrieves the results and
34## returns them. They will be returned as one cell-array with an entry
35## for each output argument. If some output arguments are marked as not
36## requested by setting some elements of @var{isout} to false, the
37## returned cell-array will only have entries for the requested output
38## arguments. For consistency, the returned cell-array can be empty. To
39## assign the output arguments to single variables, you can for example
40## use: @code{[a, b, c] = returned_cell_array@{:@};}.
41##
42## If no output argument is given to @code{rfeval}, the function does
43## not retrieve the results of the remote function call but returns
44## immediately. It is left to the user to retrieve the results with
45## @code{precv}. The results will be in the same format as if returned
46## by @code{rfeval}. Note that a cell-array, possibly empty, will always
47## have to be retrieved, even if the remote function call should have
48## been performed without output arguments.
49##
50## Parallel execution can be achieved by calling @code{rfeval} several
51## times with different specified server machines before starting to
52## retrieve the results.
53##
54## The specified function handle can refer to a function present at
55## the executing machine or be an anonymous function. See
56## @code{parallel_doc ("limitations")} for possible limitations.
57##
58## @seealso{pconnect, pserver, sclose, install_vars, netcellfun}
59## @end deftypefn
60
61function ret = rfeval (varargin)
62
63  if ((nargs = nargin ()) < 4)
64    print_usage ();
65  endif
66
67  fname = "rfeval";
68
69  if (! isa (conn = varargin{end}, "pconnections"))
70    error ("%s:  `connection' must be a parallel connections object", fname);
71  elseif (numel (conn) != 1)
72    error ("%s: exactly one connection must be specified", fname);
73  elseif (network_get_info (conn).local_machine)
74    error ("%s: client was specified instead of server");
75    ## reval() checks if specified at server side
76  endif
77
78  if (! is_function_handle (varargin{1}))
79    error ("%s: `func' must be a function handle", fname);
80  endif
81
82  if (! isnumeric (nout = varargin{end - 2}) || ! isscalar (nout) || ...
83      (nout = round (nout)) < 0)
84    error ("%s: `nout' must be a non-negative integer", fname);
85  endif
86
87  if (isempty (isout = varargin{end - 1}))
88    isout = true (1, nout);
89  elseif (! islogical (isout) || numel (isout) != nout)
90    error ("%s: `isout' must be empty or a logical with `nout' elements",
91           fname);
92  endif
93  isout = isout(:);
94
95  ## feval() isn't called remotely since it doesn't resepect ignoring of
96  ## output variables with '~'. So the function handle has to be sent
97  ## separately and a remote temporary variable has to be used for it.
98  ##
99  ## rargs = varargin(1 : end - 3); # could be used with feval
100  func = varargin{1};
101  rargs = varargin(2 : end - 3);
102
103  if (any (ign = ! isout))
104    rout = repmat ({"__pserver_tout__{%i},"}, nout, 1);
105    rout(ign) = {"~,"};
106    rout = cstrcat (rout{:});
107    rout = sprintf (rout, 1 : sum (isout));
108    rout = rout(1 : end - 1); # remove final ','
109    rout = sprintf ("[%s]", rout);
110    if (all (ign))
111      init_rout = "__pserver_tout__ = {}; ";
112    else
113      init_rout = "";
114    endif
115  else
116    rout = sprintf ("[__pserver_tout__{1:%i}]", nout);
117    init_rout = "";
118  endif
119
120  cmd = sprintf ...
121      ("__pserver_tfunc__ = precv (sockets(1)); %s%s = __pserver_tfunc__ (precv (sockets(1)){:}); psend (__pserver_tout__, sockets(1)); clear ('__pserver_tout__');",
122       init_rout, rout);
123
124  reval (cmd, conn);
125
126  ready = false;
127
128  unwind_protect
129
130    psend (func, conn);
131
132    psend (rargs, conn);
133
134    ready = true;
135
136  unwind_protect_cleanup
137
138    if (! ready)
139      sclose (conn); # might already be closed from within reval or psend
140    endif
141
142  end_unwind_protect
143
144  if (nargout () > 0)
145    ret = precv (conn);
146  endif
147
148endfunction
149