Changes between Version 37 and Version 38 of Clojure Client Tutorial


Ignore:
Timestamp:
Aug 8, 2011 11:11:40 AM (13 years ago)
Author:
kedwar10
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Clojure Client Tutorial

    v37 v38  
    119119{{{
    120120client.core=> (load-quagent)                                                                                                               
    121 :quagent240
     121:quagent277
    122122client.core=> (load-quagent "Bob")
    123123:Bob
     
    128128{{{
    129129client.core=> (get-quagents)
    130 (:Bob :quagent240)
     130#{:Bob :quagent277}
    131131}}}
    132132
     
    142142Note that control of the terminal does not return until the bot has completed his action.
    143143
    144 Now say you want to define a function on the fly to send an op to the server, the easiest way to do so is the 'client->server' function.
    145 
    146 {{{
    147 client.core=> (doc client->server)
    148 -------------------------
    149 client.protocol-one/client->server
    150 ([send-func quagent scheduling op args & fs])
    151   Provides a usable interface for sending ops to the server.
    152   Arguments are the type of send function, the quagent to
    153   execute the op, the scheduling, the op keyword
    154   (see *codes* in protocol_one.clj) the arguments to the op
    155   (in vector form) and the dispatch functions to be given to
    156   the send function.
    157 nil
    158 }}}
    159 
    160 The send function can be one of the following
    161 
    162 {{{
    163 client.core=> (doc send-and-forget)
    164 -------------------------
    165 client.protocol-one/send-and-forget
    166 ([quagent id msg])
    167   Send an op to the server and throw away the results.
    168 nil
    169 }}}
    170 
    171 {{{
    172 client.core=> (doc send-and-get)   
    173 -------------------------
    174 client.protocol-one/send-and-get
    175 ([quagent id msg] [quagent id msg f])
    176   Send a op to the server and block this thread until the
    177   op completes. Args are a (unique) op id, a msg to send
    178   to the server and a function f that determines how to
    179   combine the current reply from the server with past
    180   results (defaults to conj).
    181 nil
    182 }}}
    183 
    184 {{{
    185 client.core=> (doc send-and-get-later)
    186 -------------------------
    187 client.protocol-one/send-and-get-later
    188 ([quagent id msg] [quagent id msg f])
    189   This will spawn a separate thread to do the send and get,
    190   if you try to dereference it before it has completed,
    191   it will block.  Use future-done? to check the status before
    192   dereferencing.
    193 nil
    194 }}}
    195 
    196 {{{
    197 client.core=> (doc send-and-watch)   
    198 -------------------------
    199 client.protocol-one/send-and-watch
    200 ([quagent id msg wf] [quagent id msg f wf])
    201   Sends the op to the server and then applies f to update the value.
    202   When the value changes wf is called (wf must meet the requirements
    203   of the add-watch function).  The return value is the key for the
    204   watcher; this is used to delete it if necessary
    205  [(example)](http://clojure-examples.appspot.com/clojure.core/add-watch?revision=1278516003572).
    206 nil
    207 }}}
    208 
    209 Say we want to define a function "get-location-of" that takes one argument (a quagent key) and returns the position as a vector of doubles.
    210 From protocol zero, we know that the string to be sent to the server should look like "n lc <id>" where <id> is the op id. One possible implementation would be
    211 
    212 {{{
    213 client.core=> (defn get-location-of
    214                 [quagent]
    215                 (client->server send-and-get
    216                                 quagent
    217                                 :now
    218                                 :current-location
    219                                 nil))
    220 #'client.core/get-location-of
    221 }}}
    222 
    223 Note that the protocol zero op codes have been mapped to more user friendly keywords.  Here is the full map (subject to change)
     144Now say you want to define a function on the fly to send an op to the server, this can be done with either "send-and-forget", "send-and-get", "send-and-get-later", and "send-and-watch".  These functions provide a human-readable interface to protocol zero by mapping all of the op codes to keywords.  Here is the full list of keyword arguments subject to change).
     145
    224146* :move-indefinitely "mi"
    225147* :move-for "mf"
     
    258180* :replace "r"
    259181
    260 Running this however, didn't yield the expected output.
    261 
    262 {{{
    263 client.core=> (get-location-of :Bob)
    264 [{:data ("-128.000000" "136.000000" "40.125000"), :op "lc", :reply-type "rs", :id "1"}]
    265 }}}
    266 
    267 Instead of getting a vector of doubles, it returned a vector that contains a map.  To fix this we need to pass in
    268 an dispatch function to control the behaviour of the return value. This function takes two arguments, the previous
    269 result, and the most recent reply from the server, and determines how to combine them. For example, say we only
    270 wish to return the map.
    271 
    272 {{{
    273 client.core=> (defn get-location-of [quagent]
    274                 (client->server send-and-get
    275                                 quagent
    276                                 :now
    277                                 :current-location
    278                                 nil
    279                                 (fn [prev reply]
    280                                   reply)))
    281 #'client.core/get-location-of
    282 }}}
    283 
    284 Note the change in return value.
    285 
    286 {{{
    287 client.core=> (get-location-of :Bob)                                                                                                 
    288 {:data ("-128.000000" "136.000000" "40.125000"), :op "lc", :reply-type "rs", :id "2"}
    289 }}}
    290 
    291 Here is the correct version. Note the use of destructuring in the anonymous function
    292 
    293 {{{
    294 client.core=> (defn get-location-of [quagent]
    295                 (client->server send-and-get
    296                                 quagent
    297                                 :now
    298                                 :current-location
    299                                 nil
    300                                 (fn [prev {:keys [data]}]
    301                                   (vec (map #(Double/parseDouble %) data)))))
    302 #'client.core/get-location-of
    303 }}}
    304 
    305 Now this returns the expected output.
    306 
    307 {{{
    308 client.core=> (get-location-of :Bob)
    309 [-128.0 136.0 40.125]
    310 }}}
    311 
    312 This particular anonymous function is so frequent that that it comes predefined.
    313 
    314 {{{
    315 client.core=> (defn get-location-of [quagent] 
    316                 (client->server send-and-get
    317                                 quagent
    318                                 :now
    319                                 :current-location
    320                                 nil
    321                                 data->doubles))                           
    322 #'client.core/get-location-of
    323 }}}
    324 
    325 Suppose that this particular op took a long time to complete and we didn't want to wait around for it to complete,
    326 The way clojure handles this is through the use of "futures".  Simply, clojure will do the computation in a separate
    327 thread so the current one can continue.  send-and-get-later is identitical to send-and-get except that it is passed
    328 to the "future" function.
    329 
    330 {{{
    331 client.core=> (doc future)
    332 -------------------------
    333 clojure.core/future
    334 ([& body])
    335 Macro
    336   Takes a body of expressions and yields a future object that will
    337   invoke the body in another thread, and will cache the result and
    338   return it on all subsequent calls to deref/@. If the computation has
    339   not yet finished, calls to deref/@ will block.
    340 nil
    341 }}}
    342 
    343 We need to redefine "get-location-of" with a different send function.
    344 
    345 {{{
    346 client.core=> (defn get-location-of [quagent]
    347                 (client->server send-and-get-later ;
    348                                 quagent
    349                                 :now
    350                                 :current-location
    351                                 nil
    352                                 data->doubles))
    353 #'client.core/get-location-of
    354 }}}
    355 
    356 Now we need a variable to store the future.
    357 
    358 {{{
    359 client.core=> (def f (get-location-of :Bob))
    360 #'client.core/f
    361 }}}
    362 
    363 In order to check if the computation is done use the "future-done?" function.
    364 
    365 {{{
    366 client.core=> (doc future-done?)
    367 -------------------------
    368 clojure.core/future-done?
    369 ([f])
    370   Returns true if future f is done
    371 nil
    372 client.core=> (future-done? f)
    373 true
    374 }}}
    375 
    376 Now we know f is safe to dereference.
    377 
    378 {{{
    379 client.core=> @f
    380 [-128.0 136.0 40.125]
    381 }}}
    382 
    383 Futures allow us to do more interesting things, however. Say we want to make two bots explore a maze simultaneously,
    384 this can't be done if the quagents are all blocking on the same thread. Type control-c in the repl and execute the
    385 following.
    386 
    387 {{{
    388 client.core=> (run-ioquake "largemaze")   
    389 #<UNIXProcess java.lang.UNIXProcess@5ce40>
    390 client.core=> (load-quagent :Bob) ; remember to stand in the corner         
    391 :Bob
    392 client.core=>(make-gui) ; the quagents use a value iteration to calculate the best policy; brightness denotes higher utility                           
    393 #<JFrame javax.swing.JFrame (output truncated)>
    394 client.core=> (def q1 (future (explore-maze :Bob)))
    395 #'client.core/q1
    396 client.core=> (load-quagent :Joe)                 
    397 :Joe
    398 client.core=> (def q2 (future (explore-maze :Joe)))
    399 #'client.core/q2
    400 }}}
    401 
    402 
    403 Suppose now we need a more sophisticated behaviour whereby the quagent sends out a message to the server and then calls a function every
    404 time the server replies before terminating the op.  This can be accomplished with the last send function "send-and-watch".  Here, an
    405 additional watcher function (wf) must be specified that takes four arguments: a (k)ey, (r)efernce, (o)ld value, and (n)ew value.
    406 
    407 {{{
    408 client.core=> (defn get-location-of [quagent]
    409                 (client->server send-and-watch
    410                                 quagent
    411                                 :now
    412                                 :current-location
    413                                 nil
    414                                 (fn [k r o n]
    415                                  (println "old val " o " new val " n)))
    416 #'client.core/get-location-of
    417 client.core=> (get-location-of :Joe)                                                                                                                               
    418 :watcher245
    419 old val  []  new val  [{:data (64.000000 64.000000 32.125000), :op lc, :reply-type rs, :id 1}]
    420 }}}
    421 
    422 The key :watcher245 can be used to delete the watch function if so desired.  Otherwise it will be removed when the command completes.
    423 
    424 {{{
    425 client.core=> (doc remove-watch)
    426 -------------------------
    427 clojure.core/remove-watch
    428 ([reference key])
    429   Alpha - subject to change.
    430   Removes a watch (set by add-watch) from a reference
    431 nil
    432 }}}
    433 
    434 Recall that "send-and-watch" can also accept a function to control the combination of previous and current replies as well, so the watcher function argument "o" is not limited to just being a vector of replies.
     182{{{
     183client.core=> (doc send-and-forget)
     184-------------------------
     185client.protocol-one/send-and-forget
     186([quagent op scheduling args])
     187  Sends an op to the server and discards the results.
     188  Use the keywords in *codes* (see above) instead of
     189  protocol zero codes. The args to the op should be in
     190  a vector.
     191nil
     192}}}
     193
     194{{{
     195client.core=> (doc send-and-get)   
     196-------------------------
     197client.protocol-one/send-and-get
     198([quagent op scheduling args init f])
     199  Sends an op to the server and blocks until it returns.
     200  Replies are combined using the (init)ial value and
     201  (f)unction supplied by the user.   Use the keywords in
     202  *codes* (see above) instead of protocol zero codes.
     203  The args to the op should be in a vector.
     204nil
     205
     206}}}
     207
     208{{{
     209client.core=> (doc send-and-get-later)
     210-------------------------
     211client.protocol-one/send-and-get-later
     212([quagent op scheduling args init f])
     213  Sends an op to the server and returns a future
     214  that waits for the reply. Replies are combined
     215  using the (init)ial value and (f)unction supplied
     216  by the user.  Use the keywords in *codes* (see above)
     217  instead of protocol zero codes. The args to the op
     218  should be in a vector.
     219nil
     220
     221}}}
     222
     223{{{
     224client.core=> (doc send-and-watch)   
     225-------------------------
     226client.protocol-one/send-and-watch
     227([quagent op scheduling args init f wf])
     228  Sends an op to the server and processes replies with
     229  an (init)ial value and (f)unction supplied by the user;
     230  when the (acc)ulator value changes, wf is called.
     231  wf must be a function that accepts 4 arguments,
     232  as in (fn [reference key old-val new-val] ...).  Use
     233  the keywords in *codes* (see above) instead of protocol
     234  zero codes. The args to the op should be in a vector.
     235nil
     236
     237}}}
     238
     239Say we want to define a function "scan-area" that takes two arguments (a quagent key and a radius) and returns a hash map of the positions of the items.
    435240
    436241----
     
    450255== Current Issues ==
    451256
    452 * Exploring the maze with multiple bots will occasionally cause one to hang.  This is not an issue with server since inserting bots via separate repl's fixes the issue.
    453 
    454 
    455 
     257* Exploring the maze with multiple bots will occasionally cause one to hang.  This is an issue coordinating multiple data structures in the maze explorer code, not in protocol one.
     258
     259
     260