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. |