| 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) |
| | 144 | Now 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 | |
| 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 | {{{ |
| | 183 | client.core=> (doc send-and-forget) |
| | 184 | ------------------------- |
| | 185 | client.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. |
| | 191 | nil |
| | 192 | }}} |
| | 193 | |
| | 194 | {{{ |
| | 195 | client.core=> (doc send-and-get) |
| | 196 | ------------------------- |
| | 197 | client.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. |
| | 204 | nil |
| | 205 | |
| | 206 | }}} |
| | 207 | |
| | 208 | {{{ |
| | 209 | client.core=> (doc send-and-get-later) |
| | 210 | ------------------------- |
| | 211 | client.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. |
| | 219 | nil |
| | 220 | |
| | 221 | }}} |
| | 222 | |
| | 223 | {{{ |
| | 224 | client.core=> (doc send-and-watch) |
| | 225 | ------------------------- |
| | 226 | client.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. |
| | 235 | nil |
| | 236 | |
| | 237 | }}} |
| | 238 | |
| | 239 | Say 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. |