This guide shows how to understand and modify the code of existing HubNet activities and write your own new ones. It assumes you are familiar with running HubNet activities, basic NetLogo code and NetLogo interface elements. For more general information about HubNet see the HubNet Guide.
Many HubNet activities will share bits of the same code. That is the code that it used to setup the network and the code that is used to receive information from and send information to the clients. If you understand this code you should be able to easily make modifications to existing activities and you should have a good start on writing your own activities. To get you started we have provided a Template model (in HubNet Activities -> Code Examples) that contains the most basic components that will be in the majority of HubNet activities. You should be able to use this activity as a starting point for most projects.
To make a NetLogo model into a HubNet activity you must first
initialize the network. In most HubNet activities you will use the
startup
procedure to
initialize the network. startup
is a special procedure that
NetLogo runs automatically when you open any model. That makes it a
good place to put code that you want to run once and only once (no
matter how many times the user runs the model). For HubNet we put the
command that initializes the network in startup
because once
the network is setup we don't need to do so again. We initialize
the system using hubnet-reset
, which will
ask the user for a session name and open up the HubNet Control
Center. Here is the startup procedure in the template model:
to startup hubnet-reset end
Now that the network is all setup you don't need to worry about
calling hubnet-reset
again. Take
a look at the setup procedure in the template model:
to setup cp cd clear-output ask turtles [ set step-size 1 hubnet-send user-id "step-size" step-size ] end
For the most part it looks like most other setup procedures, however,
you should notice that it does not call clear-all
. In this model,
and in the great majority of HubNet activities in the Models Library,
we have a breed of turtles that represent the currently logged in
clients. In this case we've called this breed students
.
Whenever a client logs in we create a student and record any
information we might need later about that client in a turtle
variable. Since we don't want to require users to log out and log
back in every time we setup the activity we don't want to kill
all the turtles, instead, we want to set all the variables back to
initial values and notify the clients of any changes we make (more on
that later).
During the activity you will be transferring data between the HubNet
clients and the server. Most HubNet activities will call a procedure
in the go
loop that checks for new messages from clients in
this case it's called listen clients:
to listen-clients while [ hubnet-message-waiting? ] [ hubnet-fetch-message ifelse hubnet-enter-message? [ create-new-student ] [ ifelse hubnet-exit-message? [ remove-student ] [ execute-command hubnet-message-tag ] ] ] end
As long as there are messages in the queue this loop fetches each
message one at a time. hubnet-fetch-message
makes the next message in the queue the current message and sets the
reporters hubnet-message-source
,
hubnet-message-tag
,
and hubnet-message
to the
appropriate values. The clients send messages when the users login
and logout any time the user manipulates one of the interface
elements, that is, pushes a button, moves a slider, clicks in the
view, etc. We step through each message and decide what action to
take depending on the type of message (enter, exit, or other), the
hubnet-message-tag
(the name of the interface element), and the hubnet-message-source
of the message (the name of the client the message came from).
On an enter message we create a turtle with a user-id
that
matches the hubnet-message-source
which is the name that each user enters upon entering the activity,
it is guaranteed to be unique.
to create-new-student create-students 1 [ set user-id hubnet-message-source set label user-id set step-size 1 send-info-to-clients ] end
At this point we set any other client variables to default values and
send them to the clients if appropriate. We declared a students-own
variable for
every interface element on the client that holds state, that is,
anything that would be a global variable on the server, sliders,
choosers, switches and input boxes. It is important to make sure that
these variables stay synchronized with the values visible on the
client.
When the clients logout they send an exit message to the server which gives you a chance to clean up any information you have been storing about the client, in this case we merely have to ask the appropriate turtle to die.
to remove-student ask students with [user-id = hubnet-message-source] [ die ] end
All other messages are interface elements identified by the hubnet-message-tag
which is the name that appears in the client interface. Every time an
interface element changes a message is sent to the server. Unless you
store the state of the values currently displayed in the client
interface will not be accessible in other parts of the model.
That's why we've declared a students-own
variable for
every interface element that has a state (sliders, switches, etc).
When we receive the message from the client we set the turtle
variable to the content of the message:
if hubnet-message-tag = "step-size" [ ask students with [user-id = hubnet-message-source] [ set step-size hubnet-message ] ]
Since buttons don't have any associated data there is generally no associated turtle variable, instead they indicate an action taken by the client, just as with a regular button there is often procedure associated with each button that you call whenever you receive a message indicating the button has been pressed. Though it is certainly not required, the procedure is often a turtle procedure, that is, something that the student turtle associated with the message source can execute:
if command = "move left" [ set heading 270 fd 1 ]
As mentioned earlier you can also send values to any interface elements that display information: monitors, sliders, switches, choosers, and input boxes (note that plots and the view are special cases that have their own sections).
There are two primitives that allow you to send information hubnet-send
and hubnet-broadcast
.
Broadcast sends the information to all the clients; send sends to one
client, or a selected group.
As suggested earlier, nothing on the client updates automatically. If a value changes on the server, it is your responsibility as the activity author to update monitors on the client.
For example, say you have a slider on the client called step-size and a monitor called Step Size (note that the names must be different) you might write updating code like this:
if hubnet-message-tag = "step-size" [ ask student with [ user-id = hubnet-message-source ] [ set step-size hubnet-message hubnet-send user-id "Step Size" step-size ] ]
You can send any type of data you want, numbers, strings, lists, lists of lists, lists of strings, however, if the data is not appropriate for the receiving interface element (say, if you were to send a string to a slider) the message will be ignored. Here are a few code examples for different types of data:
data type |
hubnet-broadcast example
|
hubnet-send example
|
---|---|---|
number |
hubnet-broadcast "A" 3.14
|
hubnet-send "jimmy" "A" 3.14
|
string |
hubnet-broadcast "STR1" "HI THERE"
|
hubnet-send ["12" "15"] "STR1"
"HI THERE"
|
list of numbers |
hubnet-broadcast "L2" [1 2 3]
|
hubnet-send hubnet-message-source "L2" [1 2 3]
|
matrix of numbers |
hubnet-broadcast "[A]" [[1 2] [3 4]]
|
hubnet-send "susie" "[A]" [[1 2] [3
4]]
|
list of strings |
hubnet-broadcast "user-names" [["jimmy"
"susie"] ["bob" "george"]]
|
hubnet-send "teacher" "user-names"
[["jimmy" "susie"] ["bob"
"george"]]
|
Study the models in the "HubNet Activities" section of the Models Library to see how these primitives are used in practice in the Code tab. Disease is a good one to start with.
Open the HubNet Client Editor, found in the Tools Menu. Add any
buttons, sliders, switches, monitors, plots, choosers, or notes that
you want just as you would in the interface tab. You'll notice
that the information you enter for each of the widgets is slightly
different than in the Interface panel. Widgets on the client
don't interact with the model in the same way. Instead of a
direct link to commands and reporters the widgets send messages back
to the server and the model then determines how those messages affect
the model. All widgets on the client have a tag which is a name that
uniquely identifies the widget. When the server receives a message
from that widget the tag is found in hubnet-message-tag
For example, if you have a button called "move left", a slider called "step-size", a switch called "all-in-one-step?", and a monitor called "Location:", the tags for these interface elements will be as follows:
interface element | tag |
---|---|
move left | move left |
step-size | step-size |
all-in-one-step? | all-in-one-step? |
Location: | Location: |
Note that you can only have one interface element with a specific name. Having more than one interface element with the same tag in the client interface will result in unpredictable behavior since it is not clear which element you intended to send the information to.
View mirroring lets views of the world be displayed in clients as well on the server. View mirroring is enabled using a checkbox in the HubNet Control Center.
When mirroring is enabled, client views update whenever the view on the server does. To avoid excessive network traffic, the view should not update more often than necessary. Therefore we strongly recommend using tick-based updates, rather than continuous updates. See the View Updates section of the Programming Guide for an explanation of the two types of updates.
With tick-based updates, updates happen when a tick
or
display
command runs. We recommend using these commands only
inside an every
block, to limit the frequency of view
updates and thus also limit network traffic. For example:
every 0.1 [ display ]
If there is no View in the clients or if the Mirror 2D View on Clients checkbox in the HubNet Control Center is not checked, then no view updates are sent to the clients.
If the View is included in the client, two messages are sent to the server every time the user clicks in the view. The first message, when the user presses the mouse button, has the tag "View". The second message, sent when the user releases the mouse button, has the tag "Mouse Up". Both messages consist of a two item list of the x and y coordinates. For example, to turn any patch that was clicked on by the client red, you would use the following NetLogo code:
if hubnet-message-tag = "View" [ ask patches with [ pxcor = (round item 0 hubnet-message) and pycor = (round item 1 hubnet-message) ] [ set pcolor red ] ]
When view mirroring is enabled, by default clients see the same view the activity leader sees on the server. But you can change this so that each client sees something different, not just a literal "mirror".
You can change what a client sees in two distinct ways. We call them "client perspectives" and "client overrides".
Changing a client's perspective means making it "watch"
or "follow" a particular agent, much like the watch
and follow
commands that work with
ordinary NetLogo models. See the dictionary entries for hubnet-send-watch
,
hubnet-send-follow
,
and hubnet-reset-perspective
.
Client overrides let you change the appearance of patches, turtles,
and links in the client views. You can override any of the variables
affecting an agent's appearance, including the hidden?
variable causing a turtle or link to be visible or invisible. See the
dictionary entries for hubnet-send-override
,
hubnet-clear-override
,
and hubnet-clear-overrides
.
If plot mirroring is enabled (in the HubNet Control Center) and a plot in the NetLogo model changes and a plot with the exact same name exists on the clients, a message with that change is sent to the clients causing the client's plot to make the same change. For example, let's pretend there is a HubNet model that has a plot called Milk Supply in NetLogo and the clients. Milk Supply is the current plot in NetLogo and in the Command Center you type:
plot 5
This will cause a message to be sent to all the clients telling them that they need to plot a point with a y value of 5 in the next position of the plot. Notice, if you are doing a lot of plotting all at once, this can generate a lot of plotting messages to be sent to the clients.