Changes between Version 8 and Version 9 of Clojure Client Tutorial
- Timestamp:
- Aug 2, 2011 12:25:18 PM (14 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
Clojure Client Tutorial
v8 v9 66 66 67 67 Since I already have the path predefined, however, this will also work: 68 69 {{{ 68 70 client.core=> (run-ioquake "firstroom") 69 71 #<UNIXProcess java.lang.UNIXProcess@56c3cf> 72 }}} 70 73 71 74 You should now see ioquake running in a separate window (if you are in fullscreen mode, navigate to setup and switch this off). Now hit 72 75 backtick (`) to free the mouse from ioquake. Back in the REPL, type "(doc load-quagent)" 76 77 {{{ 73 78 client.core=> (doc load-quagent) 74 79 ------------------------- … … 77 82 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'. 78 83 nil 84 }}} 79 85 80 86 Let'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 {{{ 81 89 client.core=> (load-quagent) 82 90 :quagent240 83 91 client.core=> (load-quagent "Bob") 84 92 :Bob 93 }}} 85 94 86 95 You can check which quagents are currently loaded with the 'get-quagents' function. 96 97 {{{ 87 98 client.core=> (get-quagents) 88 99 (:Bob :quagent240) 100 }}} 89 101 90 102 You can use these keywords to make the quagents do things in the environment (Note: this functions are subject to change). 103 104 {{{ 91 105 client.core=> (face-me :Bob) 92 106 [] … … 94 108 [] 95 109 Note that control of the terminal does not return until the bot has completed his action. 110 }}} 96 111 97 112 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. 113 114 {{{ 98 115 client.core=> (doc client->server) 99 116 ------------------------- … … 102 119 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. 103 120 nil 121 }}} 104 122 105 123 The send function can be one of the following 106 124 125 {{{ 107 126 client.core=> (doc send-and-forget) 108 127 ------------------------- … … 111 130 Send an op to the server and throw away the results. 112 131 nil 113 132 }}} 133 134 {{{ 114 135 client.core=> (doc send-and-get) 115 136 ------------------------- … … 118 139 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). 119 140 nil 120 141 }}} 142 143 {{{ 121 144 client.core=> (doc send-and-get-later) 122 145 ------------------------- … … 125 148 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. 126 149 nil 127 150 }}} 151 152 {{{ 128 153 client.core=> (doc send-and-watch) 129 154 ------------------------- … … 132 157 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. 133 158 nil 159 }}} 134 160 135 161 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. 136 162 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 137 163 164 {{{ 138 165 client.core=> (defn get-location-of [quagent] (client->server send-and-get quagent :now :current-location nil)) 139 166 #'client.core/get-location-of 167 }}} 140 168 141 169 Note that the protocol zero op codes have been mapped to more user friendly keywords. Here is the full map (subject to change) … … 177 205 178 206 Running this however, didn't yield the expected output. 207 208 {{{ 179 209 client.core=> (get-location-of :Bob) 180 210 [{:data ("-128.000000" "136.000000" "40.125000"), :op "lc", :reply-type "rs", :id "1"}] 211 }}} 181 212 182 213 Instead 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 behavio r of the return value. This function takes two arguments, the previous214 an dispatch function to control the behaviour of the return value. This function takes two arguments, the previous 184 215 result, and the most recent reply from the server, and determines how to combine them. For example, say we only 185 216 wish to return the map. 186 217 218 {{{ 187 219 client.core=> (defn get-location-of [quagent] (client->server send-and-get quagent :now :current-location nil (fn [prev reply] reply))) 188 220 #'client.core/get-location-of 221 }}} 189 222 190 223 Note the change in return value. 224 225 {{{ 191 226 client.core=> (get-location-of :Bob) 192 227 {:data ("-128.000000" "136.000000" "40.125000"), :op "lc", :reply-type "rs", :id "2"} 228 }}} 193 229 194 230 Here is the correct version. Note the use of destructuring in the anonymous function 231 232 {{{ 195 233 client.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))))) 196 234 #'client.core/get-location-of 235 }}} 197 236 198 237 Now this returns the expected output. 238 239 {{{ 199 240 client.core=> (get-location-of :Bob) 200 241 [-128.0 136.0 40.125] 242 }}} 201 243 202 244 This 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 {{{ 247 client.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 }}} 205 250 206 251 Suppose that this particular op took a long time to complete and we didn't want to wait around for it to complete, … … 208 253 thread so the current one can continue. send-and-get-later is identitical to send-and-get except that it is passed 209 254 to the "future" function. 255 256 {{{ 210 257 client.core=> (doc future) 211 258 ------------------------- … … 218 265 not yet finished, calls to deref/@ will block. 219 266 nil 267 }}} 220 268 221 269 We need to redefine "get-location-of" slightly. 270 271 {{{ 222 272 client.core=> (defn get-location-of [quagent] (client->server send-and-get-later quagent :now :current-location nil data->doubles)) 223 273 #'client.core/get-location-of 274 }}} 224 275 225 276 Now we need a variable to store the future. 277 278 {{{ 226 279 client.core=> (def f (get-location-of :Bob)) 227 280 #'client.core/f 281 }}} 228 282 229 283 In order to check if the computation is done use the "future-done?" function. 284 285 {{{ 230 286 client.core=> (doc future-done?) 231 287 ------------------------- … … 234 290 Returns true if future f is done 235 291 nil 236 237 292 client.core=> (future-done? f) 238 293 true 294 }}} 239 295 240 296 Now we know f is safe to dereference. 297 298 {{{ 241 299 client.core=> @f 242 300 [-128.0 136.0 40.125] 301 }}} 243 302 244 303 Futures allow us to do more interesting things, however. Say we want to make two bots explore a maze simultaneously, 245 304 this can't be done if the quagents are all blocking on the same thread. Type control-c in the repl and execute the 246 305 following. 306 307 {{{ 247 308 client.core=> (run-ioquake "largemaze") 248 309 #<UNIXProcess java.lang.UNIXProcess@5ce40> … … 257 318 client.core=> (def q2 (future (explore-maze :Joe))) 258 319 #'client.core/q2 320 }}} 259 321 260 322 Some time later while still exploring the maze... 323 324 {{{ 261 325 client.core=> (future-done? q1) 262 326 true 263 327 client.core=> (future-done? q2) 264 328 false 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 331 Looks 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 {{{ 267 334 client.core=> (future-cancel q1) 268 335 false 269 336 client.core=> (def q1 (future (explore-maze :Bob))) 270 337 #'client.core/q1 338 }}} 271 339 272 340 Try running these without the future call and note the difference. 273 341 274 Suppose now we need a more sophisticated behavio r whereby the quagent sends out a message to the server and then calls a function every342 Suppose now we need a more sophisticated behaviour whereby the quagent sends out a message to the server and then calls a function every 275 343 time the server replies before terminating the op. This can be accomplished with the last send function "send-and-watch". Here, an 276 344 additional watcher function (wf) must be specified that takes four arguments: a (k)ey, (r)efernce, (o)ld value, and (n)ew value. 345 346 {{{ 277 347 client.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))) 278 348 #'client.core/get-location-of … … 280 350 :watcher245 281 351 old val [] new val [{:data (64.000000 64.000000 32.125000), :op lc, :reply-type rs, :id 1}] 352 }}} 282 353 283 354 The key :watcher245 can be used to delete the watch function if so desired. Otherwise it will be removed when the command completes. 355 356 {{{ 284 357 client.core=> (doc remove-watch) 285 358 ------------------------- … … 289 362 Removes a watch (set by add-watch) from a reference 290 363 nil 291 292 293 294 295 296 364 }}} 365 366 367 368 369