Changes between Version 8 and Version 9 of Clojure Client Tutorial


Ignore:
Timestamp:
Aug 2, 2011 12:25:18 PM (13 years ago)
Author:
kedwar10
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Clojure Client Tutorial

    v8 v9  
    6666
    6767Since I already have the path predefined, however, this will also work:
     68
     69{{{
    6870client.core=> (run-ioquake "firstroom")                                                                                                     
    6971#<UNIXProcess java.lang.UNIXProcess@56c3cf>
     72}}}
    7073
    7174You should now see ioquake running in a separate window (if you are in fullscreen mode, navigate to setup and switch this off).  Now hit
    7275backtick (`) to free the mouse from ioquake.  Back in the REPL, type "(doc load-quagent)"
     76
     77{{{
    7378client.core=> (doc load-quagent)
    7479-------------------------
     
    7782  This function will load a quagent into the virtual environment.  Takes a optional 'moniker' (name is a reserved word in clojure) argument that specifies the key that can be used to identify the quagent and get information about it.  Otherwise a randomly generated name will be made with the prefix 'quagent'.
    7883nil
     84}}}
    7985
    8086Let's load a quagent into the map (note that if there is only one spawnpoint on the map, you'll want to move forward a bit in order to avoid getting telefragged).
     87
     88{{{
    8189client.core=> (load-quagent)                                                                                                               
    8290:quagent240
    8391client.core=> (load-quagent "Bob")
    8492:Bob
     93}}}
    8594
    8695You can check which quagents are currently loaded with the 'get-quagents' function.
     96
     97{{{
    8798client.core=> (get-quagents)
    8899(:Bob :quagent240)
     100}}}
    89101
    90102You can use these keywords to make the quagents do things in the environment (Note: this functions are subject to change).
     103
     104{{{
    91105client.core=> (face-me :Bob)
    92106[]
     
    94108[]
    95109Note that control of the terminal does not return until the bot has completed his action.
     110}}}
    96111
    97112Now 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.
     113
     114{{{
    98115client.core=> (doc client->server)
    99116-------------------------
     
    102119  Provides a usable interface for sending ops to the server.  Arguments are the type of send function, the quagent to execute the op, the scheduling, the op keyword (see *codes* in protocol_one.clj) the arguments to the op (in vector form) and the dispatch functions to be given to the send function.
    103120nil
     121}}}
    104122
    105123The send function can be one of the following
    106124
     125{{{
    107126client.core=> (doc send-and-forget)
    108127-------------------------
     
    111130  Send an op to the server and throw away the results.
    112131nil
    113 
     132}}}
     133
     134{{{
    114135client.core=> (doc send-and-get)   
    115136-------------------------
     
    118139  Send a op to the server and block this thread until the op completes. Args are a (unique) op id, a msg to send to the server and a function f that determines how to combine the current reply from the server with past results (defaults to conj).
    119140nil
    120 
     141}}}
     142
     143{{{
    121144client.core=> (doc send-and-get-later)
    122145-------------------------
     
    125148  This will spawn a separate thread to do the send and get, if you try to dereference it before it has completed, it will block.  Use future-done? to check the status before dereferencing.
    126149nil
    127 
     150}}}
     151
     152{{{
    128153client.core=> (doc send-and-watch)   
    129154-------------------------
     
    132157  Sends the op to the server and then applies f to update the value.  When the value changes wf is called (wf must meet the requirements of the add-watch function).  The return value is the key for the watcher; this is used to delete it if necessary [(example)](http://clojure-examples.appspot.com/clojure.core/add-watch?revision=1278516003572).  No ops of this type are currently supported by the server.
    133158nil
     159}}}
    134160
    135161Say 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.
    136162From 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
    137  
     163
     164{{{
    138165client.core=> (defn get-location-of [quagent] (client->server send-and-get quagent :now :current-location nil))
    139166#'client.core/get-location-of
     167}}}
    140168
    141169Note that the protocol zero op codes have been mapped to more user friendly keywords.  Here is the full map (subject to change)
     
    177205
    178206Running this however, didn't yield the expected output.
     207
     208{{{
    179209client.core=> (get-location-of :Bob)
    180210[{:data ("-128.000000" "136.000000" "40.125000"), :op "lc", :reply-type "rs", :id "1"}]
     211}}}
    181212
    182213Instead of getting a vector of doubles, it returned a vector that contains a map.  To fix this we need to pass in
    183 an dispatch function to control the behavior of the return value. This function takes two arguments, the previous
     214an dispatch function to control the behaviour of the return value. This function takes two arguments, the previous
    184215result, and the most recent reply from the server, and determines how to combine them. For example, say we only
    185216wish to return the map.
    186217
     218{{{
    187219client.core=> (defn get-location-of [quagent] (client->server send-and-get quagent :now :current-location nil (fn [prev reply] reply)))
    188220#'client.core/get-location-of
     221}}}
    189222
    190223Note the change in return value.
     224
     225{{{
    191226client.core=> (get-location-of :Bob)                                                                                                 
    192227{:data ("-128.000000" "136.000000" "40.125000"), :op "lc", :reply-type "rs", :id "2"}
     228}}}
    193229
    194230Here is the correct version. Note the use of destructuring in the anonymous function
     231
     232{{{
    195233client.core=> (defn get-location-of [quagent] (client->server send-and-get quagent :now :current-location nil (fn [prev {:keys [data]}] (vec(map #(Double/parseDouble %) data)))))
    196234#'client.core/get-location-of
     235}}}
    197236
    198237Now this returns the expected output.
     238
     239{{{
    199240client.core=> (get-location-of :Bob)
    200241[-128.0 136.0 40.125]
     242}}}
    201243
    202244This particular anonymous function is so frequent that that it comes predefined.
    203 client.core=> (defn get-location-of [quagent] (client->server send-and-get quagent :now :current-location ni data->doubles))                           
    204 #'client.core/get-location-of
     245
     246{{{
     247client.core=> (defn get-location-of [quagent] (client->server send-and-get quagent :now :current-location nil data->doubles))                           
     248#'client.core/get-location-of
     249}}}
    205250
    206251Suppose that this particular op took a long time to complete and we didn't want to wait around for it to complete,
     
    208253thread so the current one can continue.  send-and-get-later is identitical to send-and-get except that it is passed
    209254to the "future" function.
     255
     256{{{
    210257client.core=> (doc future)
    211258-------------------------
     
    218265  not yet finished, calls to deref/@ will block.
    219266nil
     267}}}
    220268
    221269We need to redefine "get-location-of" slightly.
     270
     271{{{
    222272client.core=> (defn get-location-of [quagent] (client->server send-and-get-later quagent :now :current-location nil data->doubles))
    223273#'client.core/get-location-of
     274}}}
    224275
    225276Now we need a variable to store the future.
     277
     278{{{
    226279client.core=> (def f (get-location-of :Bob))
    227280#'client.core/f
     281}}}
    228282
    229283In order to check if the computation is done use the "future-done?" function.
     284
     285{{{
    230286client.core=> (doc future-done?)
    231287-------------------------
     
    234290  Returns true if future f is done
    235291nil
    236 
    237292client.core=> (future-done? f)
    238293true
     294}}}
    239295
    240296Now we know f is safe to dereference.
     297
     298{{{
    241299client.core=> @f
    242300[-128.0 136.0 40.125]
     301}}}
    243302
    244303Futures allow us to do more interesting things, however. Say we want to make two bots explore a maze simultaneously,
    245304this can't be done if the quagents are all blocking on the same thread. Type control-c in the repl and execute the
    246305following.
     306
     307{{{
    247308client.core=> (run-ioquake "largemaze")   
    248309#<UNIXProcess java.lang.UNIXProcess@5ce40>
     
    257318client.core=> (def q2 (future (explore-maze :Joe)))
    258319#'client.core/q2
     320}}}
    259321
    260322Some time later while still exploring the maze...
     323
     324{{{
    261325client.core=> (future-done? q1)
    262326true
    263327client.core=> (future-done? q2)
    264328false
    265 
    266 Looks like something went wrong with q1 (the server sometimes stops responding after many calls).  Fortunately this is no problem for futures as they can be restarted.
     329}}}
     330
     331Looks like something went wrong with q1 (the server sometimes stops responding after many calls).  Fortunately this is no problem for the quagents as they can be restarted.
     332
     333{{{
    267334client.core=> (future-cancel q1)
    268335false
    269336client.core=> (def q1 (future (explore-maze :Bob)))
    270337#'client.core/q1
     338}}}
    271339
    272340Try running these without the future call and note the difference. 
    273341
    274 Suppose now we need a more sophisticated behavior whereby the quagent sends out a message to the server and then calls a function every
     342Suppose now we need a more sophisticated behaviour whereby the quagent sends out a message to the server and then calls a function every
    275343time the server replies before terminating the op.  This can be accomplished with the last send function "send-and-watch".  Here, an
    276344additional watcher function (wf) must be specified that takes four arguments: a (k)ey, (r)efernce, (o)ld value, and (n)ew value.
     345
     346{{{
    277347client.core=> (defn get-location-of [quagent] (client->server send-and-watch quagent :now :current-location nil (fn [k r o n](println "old val " o " new val " n)))
    278348#'client.core/get-location-of
     
    280350:watcher245
    281351old val  []  new val  [{:data (64.000000 64.000000 32.125000), :op lc, :reply-type rs, :id 1}]
     352}}}
    282353
    283354The key :watcher245 can be used to delete the watch function if so desired.  Otherwise it will be removed when the command completes.
     355
     356{{{
    284357client.core=> (doc remove-watch)
    285358-------------------------
     
    289362  Removes a watch (set by add-watch) from a reference
    290363nil
    291 
    292 
    293 
    294 
    295 
    296 
     364}}}
     365
     366
     367
     368
     369