@@ -0,0 +1,948 @@
5+This app stores a number for each user. You can increment your number, increment
6+someone else's number, start tracking someone else's number by poking. It
7+exposes a scry namespace to inspect everyone's number.
9+### `app/example-gall.hoon`
12+:: Import sur/example-gall
14+:: Import lib/default-agent
17++$ card card:agent:gall
21+ [%agent [=ship name=term] =task:agent:gall]
23++$ state-zero [%0 local=@ud]
24++$ state-one [%1 local=@ud remote=(map @p @ud)]
36+ def ~(. (default-agent this %|) bowl)
38+:: Set local counter to 1 by default
42+:: Expose state for saving
46+:: Load old state and upgrade if neccessary
50+ =/ loaded=versioned-state
51+ !<(versioned-state old)
54+ `this(local local.loaded) :: Upgrade old state
60+:: App can be poked in the dojo by running the following commands
61+:: Increment local counter
62+:: :example-gall &example-gall-action [%increment ~]
63+:: Increment ~zod's counter
64+:: :example-gall &example-gall-action [%increment-remote ~zod]
65+:: Subscribe to ~zod's counter
66+:: :example-gall &example-gall-action [%view ~zod]
67+:: Unsubscribe from ~zod's counter
68+:: :example-gall &example-gall-action [%stop-view ~zod]
73+ :: Ensure poke is of mark %example-gall-action
74+ ?> =(mark %example-gall-action)
75+ =/ act=example-gall-action !<(example-gall-action vase)
78+ :: Increment local counter and send new counter to subscribers
81+ :- [%give %fact `/local %atom !>(+(local))]~
84+ :: Send remote %increment poke
86+ [%increment-remote who=@p]
90+ /inc/(scot %p who.act)
92+ [who.act %example-gall]
93+ %poke %example-gall-action !>([%increment ~])
96+ :: Subscribe to a remote counter
100+ [%pass /view/(scot %p who.act) %agent [who.act %example-gall] %watch /local]~
102+ :: Unsubscribe from remote counter and remove from state
105+ :_ this(remote (~(del by remote) who.act))
106+ [%pass /view/(scot %p who.act) %agent [who.act %example-gall] %leave ~]~
110+:: Print on unsubscribe
114+ ^- (quip card _this)
115+ ~& "Unsubscribed by: {<src.bowl>} on: {<path>}"
118+:: Handle new subscription
120+:: When another ship subscribes to our counter, give them the current state of
121+:: the counter immediately
125+ ^- (quip card _this)
127+ :: Crash if we see a subscription we don't recognise
128+ ?+ path ~|("unexpected subscription" !!)
131+ [%give %fact ~ %atom !>(local)]~
134+:: Expose scry namespace
136+:: .^(@ %gx /=example-gall=/local/atom) will produce the current local counter
137+:: .^(@ %gx /=example-gall=/remote/~zod/atom) will produce the counter for ~zod
138+:: .^(arch %gy /=example-gall=/remote) will produce a listing of the current
142+ ^- (unit (unit cage))
145+ :: Produce local counter
150+ :: Produce remote counter
152+ [%x %remote who=@ta ~]
153+ =* location i.t.t.path :: Ship name is third in the list
155+ (~(got by remote) (slav %p location))
158+ :: Produce listing of remote counters
162+ %- molt :: Map from list of k-v pairs
163+ %+ turn :: iterate over list of k-v pairs
164+ ~(tap by remote) :: list of k-v pairs from map
167+ ``[%arch !>(`arch`[~ dir])]
170+:: Handle sign from agent
173+ |= [=wire =sign:agent:gall]
174+ ^- (quip card _this)
177+ :: Print error if poke failed
185+ :: Print error if subscription failed
190+ =/ =tank leaf+"subscribe failed from {<dap.bowl>} on wire {<wire>}"
191+ %- (slog tank u.p.sign)
194+ :: Do nothing if unsubscribed
198+ :: Update remote counter when we get a subscription update
202+ ?. ?=(%atom p.cage.sign)
204+ this(remote (~(put by remote) src.bowl !<(@ q.cage.sign)))
209+:: We never give any cards to arvo. Therefore we never need to handle any signs
210+:: from arvo. We use the default-agent library to avoid implementing this arm,
211+:: as gall apps must have all the arms.
213+++ on-arvo on-arvo:def
217+:: Print errors when they happen
221+ ^- (quip card _this)
222+ %- (slog leaf+"error in {<dap.bowl>}" >term< tang)
228+### `sur/example-gall.hoon`
232++$ example-gall-action
236+ [%increment-remote who=@p]
242+### `mar/example-gall/action.hoon`
246+|_ act=example-gall-action
249+ ++ noun example-gall-action
258+Application initialisation
260+`++on-init` is called when an application is first started. It is not a gate and
269+List of cards and new agent
274+:: From reference agent.
275+:: Set local counter to 1 by default
278+ ^- (quip card _this)
280+:: From app/weather.hoon. This setup is typical of a landscape tile.
284+ :~ [%pass /bind/weather %arvo %e %connect [~ /'~weather'] %weather]
285+ :* %pass /launch/weather %agent [our.bol %launch] %poke
286+ %launch-action !>([%weather /weathertile '/~weather/js/tile.js'])
293+Expose state for saving.
295+This arm is called immediately before the agent is upgraded. It packages the
296+permament state of the agent in a vase for the next version of the agent.
297+Unlike most handlers, this cannot produce effects. It is not a gate and has no input.
301+`vase` of permanent state.
306+:: From reference agent
308+:: Expose state for saving
318+This arm is called immediately after the agent is upgraded. It receives
319+a vase of the state of the previously-running version of the agent, obtained
320+from `+on-save`, which allows it to cleanly upgrade from the old agent.
328+`vase` of previous state, from `++on-save`
336+List of cards and new agent
341+:: From reference agent
343+:: Load old state and upgrade if neccessary
346+ ^- (quip card _this)
347+ =/ loaded=versioned-state
348+ !<(versioned-state old)
351+ `this(local local.loaded) :: Upgrade old state
359+Handle application poke.
361+This arm is called when the agent is "poked". The input is a cage, so
362+it's a pair of a mark and a dynamic vase.
370+`mark` is the mark of the poked data.
372+`vase` is a vase with the poked data inside.
380+List of cards and new agent.
385+:: From reference agent.
388+:: App can be poked in the dojo by running the following commands
389+:: Increment local counter
390+:: :example-gall &example-gall-action [%increment ~]
391+:: Increment ~zod's counter
392+:: :example-gall &example-gall-action [%increment-remote ~zod]
393+:: Subscribe to ~zod's counter
394+:: :example-gall &example-gall-action [%view ~zod]
395+:: Unsubscribe from ~zod's counter
396+:: :example-gall &example-gall-action [%stop-view ~zod]
400+ ^- (quip card _this)
401+ :: Ensure poke is of mark %example-gall-action
402+ ?> =(mark %example-gall-action)
403+ =/ act=example-gall-action !<(example-gall-action vase)
406+ :: Increment local counter and send new counter to subscribers
409+ :- [%give %fact `/local %atom !>(+(local))]~
410+ this(local +(local))
412+ :: Send remote %increment poke
414+ [%increment-remote who=@p]
418+ /inc/(scot %p who.act)
420+ [who.act %example-gall]
421+ %poke %example-gall-action !>([%increment ~])
424+ :: Subscribe to a remote counter
428+ [%pass /view/(scot %p who.act) %agent [who.act %example-gall] %watch /local]~
430+ :: Unsubscribe from remote counter and remove from state
433+ :_ this(remote (~(del by remote) who.act))
434+ [%pass /view/(scot %p who.act) %agent [who.act %example-gall] %leave ~]~
441+Handle new subscriber.
443+This arm is called when a program wants to subscribe to the agent on a
444+particular path. The agent may or may not need to perform setup steps
445+to intialize the subscription. It may produce a `%give`
446+`%subscription-result` to the subscriber to get it up to date, but after
447+this event is complete, it cannot give further updates to a specific
448+subscriber. It must give all further updates to all subscribers on a
451+If this arm crashes, then the subscription is immediately terminated.
452+More specifcally, it never started -- the subscriber will receive a
453+negative `%watch-ack`. You may also produce an explicit `%kick` to
454+close the subscription without crashing -- for example, you could
455+produce a single update followed by a `%kick`.
463+Path of new subscription.
471+List of cards and new agent.
476+:: From reference agent
478+:: Handle new subscription
480+:: When another ship subscribes to our counter, give them the current state of
481+:: the counter immediately
485+ ^- (quip card _this)
487+ ?+ path on-watch:def
490+ [%give %fact ~ %atom !>(local)]~
498+This arm is called when a program becomes unsubscribed to you.
499+Subscriptions may close because the subscriber intentionally
500+unsubscribed, but they also could be closed by an intermediary. For
501+example, if a subscription is from another ship which is currently
502+unreachable, Ames may choose to close the subscription to avoid queueing
503+updates indefinitely. If the program crashes while processing an
504+update, this may also generate an unsubscription. You should consider
505+subscriptions to be closable at any time.
513+Path of the closed subscription.
521+List of cards and new agent.
526+:: From reference agent. Prints a message when programs become unsubscribed.
529+ ^- (quip card _this)
530+ ~& "Unsubscribe by: {<src.bowl>} on: {<path>}"
538+This arm is called when a program reads from the agent's "scry"
539+namespace, which should be referentially transparent. Unlike most
540+handlers, this cannot perform IO, and it cannot change the state. All
541+it can do is produce a piece of data to the caller, or not.
549+The path being scryed for.
552+:: Example scry to path mappings
554+.^(arch %gy /=example-gall=/remote)
555+:: Path will be /y/remote
556+.^(@ %gx /=example-gall=/local/atom)
557+:: Path will be /x/local
566+If this arm produces `[~ ~ data]`, then `data` is the value at the the
567+given path. If it produces `[~ ~]`, then there is no data at the given
568+path and never will be. If it produces `~`, then we don't know yet whether
569+there is or will be data at the given path. The head of the path is known as the
570+`care`. Requests with a care of `%x` should return a vase that matches or is
571+convertible to the mark at the end of the scry request. This mark is not
572+included in the path passed to `++on-peek`. Requests with a care of `%y` should
573+return a cage with a mark of `%arch` and a vase of `arch`.
578+:: From reference agent
580+:: Expose scry namespace
582+:: .^(@ %gx /=example-gall=/local/atom) will produce the current local counter
583+:: .^(@ %gx /=example-gall=/remote/~zod/atom) will produce the counter for ~zod
584+:: .^(arch %gy /=example-gall=/remote) will produce a listing of the current
588+ ^- (unit (unit cage))
591+ :: Produce local counter
596+ :: Produce remote counter
598+ [%x %remote who=@ta ~]
599+ =* location i.t.t.path :: Ship name is third in the list
601+ (~(got by remote) (slav %p location))
604+ :: Produce listing of remote counters
608+ %- molt :: Map from list of k-v pairs
609+ %+ turn :: iterate over list of k-v pairs
610+ ~(tap by remote) :: list of k-v pairs from map
613+ ``[%arch !>(`arch`[~ dir])]
621+This arm is called to handle responses to `%pass` cards to other agents.
622+It will be one of the following types of response:
624+- `%poke-ack`: acknowledgment (positive or negative) of a poke. If the
625+ value is `~`, then the poke succeeded. If the value is `[~ tang]`,
626+ then the poke failed, and a printable explanation (eg a stack trace)
627+ is given in the `tang`.
629+- `%watch-ack`: acknowledgment (positive or negative) of a subscription.
630+ If negative, the subscription is already ended (technically, it never
633+- `%fact`: update from the publisher.
635+- `%kick`: notification that the subscription has ended. This happens because
636+ either the target app passed a `%leave` note, or ames killed the subscription
637+ due to backpressure. Most of the time you will want to resubscribe. If you can
638+ no longer access the subscription you will get a negative `%watch-ack` and end
644+[=wire =sign:agent:gall]
647+`wire` is the wire from the `+gift` that triggered `++on-agent`
649+`sign` is response for the gift.
660+:: From reference agent
662+:: Handle sign from agent
665+ |= [=wire =sign:agent:gall]
666+ ^- (quip card _this)
669+ :: Print error if poke failed
677+ :: Print error if subscription failed
682+ =/ =tank leaf+"subscribe failed from {<dap.bowl>} on wire {<wire>}"
683+ %- (slog tank u.p.sign)
686+ :: Do nothing if unsubscribed
690+ :: Update remote counter when we get a subscription update
694+ ?. ?=(%atom p.cage.sign)
696+ this(remote (~(put by remote) src.bowl !<(@ q.cage.sign)))
704+This arm is called to handle responses for `%pass` cards to vanes.
709+ [=wire =sign:agent:gall]
712+`wire` is the wire from the `++gift` that triggered `++on-arvo`.
714+`sign` is the response from the vane. The list of possible responses from the
715+vanes is statically defined in sys/zuse.hoon (grep for `++ sign-arvo`).
723+List of cards and new agent.
728+:: From app/weather.hoon. Handles %bound, %wake and %http-response signs
730+ |= [=wire =sign-arvo]
731+ ^- (quip card:agent:gall _this)
732+ ?: ?=(%bound +<.sign-arvo)
734+ ?: ?=(%wake +<.sign-arvo)
736+ (wake:wc wire error.sign-arvo)
738+ ?: ?=(%http-response +<.sign-arvo)
740+ (http-response:wc wire client-response.sign-arvo)
742+ (on-arvo:def wire sign-arvo)
744+:: From reference agent
748+:: We never give any cards to arvo. Therefore we never need to handle any signs
749+:: from arvo. We use the default-agent library to avoid implementing this arm,
750+:: as gall apps must have all the arms specified in the agent:gall definition.
752+++ on-arvo on-arvo:def
759+If an error happens in `+on-poke`, the crash report goes into the
760+`%poke-ack` response. Similarly, if an error happens in
761+`+on-subscription`, the crash report goes into the `%watch-ack`
762+response. If a crash happens in any of the other handlers, the report
763+is passed into this arm.
771+`term` is a cord describing the error.
773+`tang` is a stack trace for the error.
781+List of cards and new agent
789+:: Print errors when they happen
793+ ^- (quip card _this)
794+ %- (slog leaf+"error in {<dap.bowl>}" >term< tang)
800+Giving a gift takes the general form of
808+Produce a subscription update.
810+Produces a subscription update. A subscription update is a new piece of
811+subscription content for all subscribers on a given path.
816+[%fact (unit path) =cage]
819+`(unit path)` is the path of the subscription being updated. If no path is
820+ given, then the update is only given to the program that instigated the
821+ request. Typical use of this mode is in `+on-watch` to give an initial update
822+ to a new subscriber to get them up to date.
824+`cage` is a cage of the subscription update.
829+:: From ++on-watch in reference agent.
831+:: Gives current local state to new subscribers.
832+[%give %fact ~ %atom !>(local.sta)]
839+Closes a subscription. A subscription close closes the subscription for all or
840+one subscribers on a given path.
845+[%kick (unit path) (unit ship)]
848+`(unit path)` is the path of the subscription being updated. If no path is
849+given, then the update is only given to the program that instigated the request.
850+Typical use of this mode would be in `+on-watch` to produce a single update to a
851+subscription then close the subscription.
853+`(unit ship)` is the ship to close the subscription for. If no path is given,
854+then the subscription is closed for all subscribers.
858+Passing a agent note (a 'task') along a wire looks like so.
861+[%pass =wire %agent [=ship name=term] =task]
864+`wire` is used to identify the response to the note.
866+`ship` is the ship to pass the note to.
868+`name` is the name of the agent that should receive the note.
870+`task` is the task itself, described below.
876+This note is passed to poke an application with a cage, a marked vase.
884+`cage` is the marked data to poke the application with. It is a pair of a mark
890+:: From ++on-poke in reference agent.
892+:: Sends an increment poke to the example-gall agent
896+ /inc/(scot %p who.act)
898+ [who.act %example-gall]
899+ %poke %example-gall-action !>([%increment ~])
905+Subscribe to an application.
907+This note is given to subscribe to an application at a path
915+`path` is the path to be subscribed to.
920+:: From ++on-poke in reference agent.
922+:: Subscribes to the example-gall agent on who.act on the path /local
924+[%pass /view/(scot %p who.act) %agent [who.act %example-gall] %watch /local]
929+Unsubscribe from an application.
931+ This note is passed to unsubscribe from an application. It should be passed on
932+the same wire that the corresponding `%watch` note for the subscription was
944+:: From ++on-poke in reference agent.
946+:: Unsubscribes from the example-gall agent on who.act
947+[%pass /view/(scot %p who.act) %agent [who.act %example-gall] %leave ~]