Sinalgo - Simulator for Network Algorithms

Node Implementation

The implementation of a project consists of several tasks:
  • Implement the node behavior
  • Implement additional models as needed
  • Configure the project using the configuration file

Node Basics

Each simulated node is an instance of a subclass of sinalgo.nodes.Node. Each node has its proper instances of the Mobility, Connectivity, Interference, and Reliability model. These four model instances are created when the node is created. The Message Transmission Model is globally unique, and the distribution model is only used to initially place the nodes and is created for each set of generated nodes.

As in reality, the nodes implement their own behavior. Among others, they have a method that is called when the node receives a message, and they implement the functionality to send messages to neighboring nodes. Depending on the simulation mode, the node's methods are called in a slightly different way. The following shows a high-level picture of the calling-sequences of the synchronous and asynchronous mode, which are described in more detail in the Architecture section of this tutorial.

Calling Sequence in Synchronous Simulation

In the synchronous simulation mode, the framework performs the following actions for each round. Bold text indicates methods that you may overwrite or implement to define the behavior of the network nodes and the simulation. Note that the execution is strictly sequential, i.e. a single thread executes the following actions. As a result, no synchronization is needed to access global information.
  1. The framework increments the global time by 1.
  2. CustomGlobal.preRound(); (Optional, project specific code. This method is called at the beginning of every round.)
  3. The framework handles global timers that fire in this round.
  4. The framework moves the nodes according to their mobility models, if mobility is enabled.
  5. The framework calls each node to update its set of outgoing connections according to its connectivity models.
  6. The framework calls interference tests for all messages being sent, if interference is enabled.
  7. The framework iterates over all nodes and calls Node.step() on each node. The method 'step' performs the following actions for each node:
    1. The node gathers all messages that arrive in this round.
    2. Node.preStep(); (Optional, project specific code. This method is called at the beginning of very step.)
    3. If this node's set of outgoing connections has changed in this round, the node calls Node.neighborhoodChange();
    4. The node handles timers that fire in this round.
    5. Node.handleNAckMessages(); (Handle dropped messages, if generateNAckMessages is enabled.)
    6. Node.handleMessages(); (Handle the arriving messages.)
    7. Node.postStep(); (Optional, project specific code. This method is called at the end of each step.)
  8. CustomGlobal.postRound(); (Optional, project specific code. This method is called at the end of every round.)
  9. If CustomGlobal.hasTerminated(); returns true, the simulation exits.

Events in Asynchronous Simulation

In asynchronous simulation mode, there are only two events upon which nodes react: Arriving messages and timer events. Thus, only the methods Node.handleMessages(), (Node.handleNAckMessages()), and Timer.fire() are called. Before performing a scheduled event, the global time of the system is set to the time when the event happens.

Remember that mobility is not possible in the asynchronous mode. However, the messages may be checked for interference if interference is turned on in the configuration file.

Node Behavior

To implement the node behavior (this includes your algorithm), create a class which inherits from sinalgo.nodes.Node and place the source file in the node/nodeImplementation/ folder of your project. Implement the method Node.handleMessages(); and optionally any of the other abstract methods from the sinalgo.nodes.Node class.

The following list gives the most useful members of the sinalgo.nodes.Node class you may use. For a complete description of their functionality, refer to the documentation in the code.

Public Member Variables
int ID Each node is assigned a unique ID when it is created. This ID may be used to distinguish the nodes.
Connections outgoingConnections; A collection of all edges outgoing from this node. Note that all edges are directed, the bidirectional edges just ensure that there is an edge in both directions.
Methods
void send(Message m, int target) throws NoConnectionException; Sends a message to a neighbor node with the default intensity of the node.
void send(Message m, int target, double intensity) throws NoConnectionException; Sends a message to a neighbor node with the given intensity.
void send(Message m, Node target) throws NoConnectionException; Sends a message to a neighbor node with the default intensity of the node.
void send(Message m, Node target, double intensity) throws NoConnectionException; Sends a message to a neighbor node with the given intensity.
void sendDirect(Message msg, Node target); Sends a message to any node in the network, independent of whether there is a connection between the two nodes or not.
void broadcast(Message m); Broadcasts a message to all neighboring nodes with the default intensity of the node.
void broadcast(Message m, double intensity); Broadcasts a message to all neighboring nodes with the given intensity.
Position getPosition(); Returns the current position of the node.
TimerCollection getTimers(); Returns a collection of all timers currently active at the node.
void setRadioIntensity(double i); Sets the radio intensity of the node.
double getRadioIntensity(); Gets the radio intensity of the node.
void setColor(Color c); Sets the color in which the node is painted on the GUI.
Color getColor(); Gets the color in which the node is painted on the GUI.
void draw(...); Implements how the node is drawn on the GUI. You may overwrite this method in your subclass of sinalgo.node.Node to define a customized drawing.
void drawAsDisk(..., int sizeInPixels); A helper method provided by sinalgo.node.Node that draws the node as a disk. Call this method in your draw(...) method.
void drawNodeWithText(..., String text, int fontSize, Color textColor); A helper method provided by sinalgo.node.Node that draws the node as a disk and with text. Call this method in your draw(...) method.
void drawToPostScript(...); Implements how the node is exported to PostScript. You may overwrite this method in your subclass of sinalgo.node.Node to define a customized drawing to PostScript.
Methods of the superclass sinalgo.nodes.Node you may use to implement the node behavior.

To control the creation of a node object, the super-class provides the two methods init() and checkRequirements() which you may overwrite in your subclass:
Node.init() is called once at the beginning of the lifecycle of a node object. It may be used to initialize the start state of the node. Note that this function may not depend on the neighborhood of the node as the init function is called before the connections are set up and before the set of all nodes is available.
Node.checkRequirements() is called after the init() method to check whether all requirements to use this node type are met. This may include a test whether appropriate models have been selected.

Project Specific Popup Methods

To facilitate interaction in the GUI mode, you may register methods of your Node subclass to a popup menu that shows up when the user right-clicks on a node.
The annotation @NodePopupMethod(menuText="XXX") in the following code sample declares the method myPopupMenu() to be included in the popup menu with the menu text XXX. Note that the methods to register with the popup menu may not take any parameters and need to be located in the source-file of the specific Node subclass.

@NodePopupMethod(menuText="Multicast 2")
public void myPopupMethod() {
IntMessage msg = new IntMessage(2);
MessageTimer timer = new MessageTimer(msg);
timer.startRelative(1, this);
}
Image of popup menu
Customized node popup menu

The sample code generates a message carrying an int-value, and broadcasts it to all its neighbors. Note that the method does not broadcast the message directly, but creates a timer, which will be triggered in the next round when the node performs its step. This is necessary for the synchronous simulation mode, because nodes are only allowed to send messages while they are executing their step. However, the user can only interact with the GUI while the simulation is not running. Therefore, the methods called through the popup menu always execute when the simulation is stopped. The preferred solution is to create a timer which fires in the next round and performs the desired action.

Note: The MessageTimer is available in the defaultProject. This timer may send a unicast message to a given node, or multicast a message to all immediate neighbors. Please consult the documentation of the source code for more details.

In some cases, it may be desirable to determine only at runtime the set of methods to be included in the menu, and on their menu text. This is possible because the popup menu for the node is assembled every time the user right-clicks on a node. The framework includes all methods annotated with the NodePopupMenu annotation of the corresponding node class. But before including such a method in the list, the framework calls the node-method includeMethodInPopupMenu(Method m, String defaultText), which allows to decide at runtime whether the menu should be included or not, and, change the menu text if necessary.

To obtain control over the included menu entries, overwrite the includeMethodInPopupMenu(Method m, String defaultText) method in your node subclass. Return null if the method should not be included, otherwise the menu text to be displayed.

Messages

Nodes communicate by the means of messages. To implement your own message class, derive from sinalgo.nodes.messages.Message and place your source file in the nodes/messages/ folder of your project.

The abstract class Message requires you to implement a single method that returns a clone of the message, i.e. an exact copy of the message object:
public Message clone()

Implementation Note: When a node sends a message to a neighbor node, it is assumed that the destination receives the message-content that was sent through the send() method. The framework has however no means to test whether the sender still has a reference to the sent message-object, and therefore may be able to alter its content. To avoid such problems, the framework sends separate copies to all receivers of a send() or multicast() call. Thus, for a multicast to n neighbors, the framework obtains n copies of the message and sends a copy to each of the neighbors.

If and only if your project ensures that a message-object is not altered after it was sent, you may omit the copying process by providing the following implementation of the clone() method. (Note that the process of sending or receiving a message does not alter the message-object. Thus, a node may safely forward the same message-object it has received.)

public Message clone() {
return this;// This message requires a read-only policy
}

Inbox / handleMessages()

Each node stores the messages it receives in an instance of the Inbox class. The inbox provides an iterator-like view over the set of messages that are received in the current round in synchronous simulation mode. In asynchronous simulation mode, the inbox contains only the single message that triggered the event. The method handleMessages(Inbox inbox) provides this inbox as single parameter.

For each received message, this iterator stores meta-information, such as the sender of the message. This meta-information is available for the packet that was last returned through the next() method.

In order to iterate several times over the set of packets, you may reset the inbox by calling reset(), size() returns the number of messages in the inbox. Call remove() to remove the message from the inbox that was returned by the last call to next().

Typically, a node iterates over all messages in the inbox with the following code:

while(inbox.hasNext()) {
Message msg = inbox.next();
if(msg instanceof ...) {
\\ handle this type of message
}
}

NackBox / handleNAckMessages()

Messages may be dropped, due to changes to the connectivity graph, interference, or the message transmission model. The framework provides a means to inform the sender node whenever a unicast message sent by the node does not reach the destination. This is an optional feature that needs to be enabled through the project configuration: set the entry generateNAckMessages to true. If your project does not drop messages at all, or if your nodes are not interested in this information, you should disable this feature to speed up the simulation.

In asynchronous simulation mode, messages are kept in message-events, which are scheduled to execute when the message is supposed to arrive. At the time of execution, the framework decides whether the message arrives. If the message arrives, the method handleMessages() is called on the receiver node. If the message does not arrive, the method handleNAckMessages() is called on the sender node.

In synchronous simulation mode, a sender node can handle the set of messages that were scheduled to arrive in the previous round, but were dropped. The method handleNAckMessages() is called prior to handling the messages that arrive on the node, and passes on the set of dropped messages.

The use of the NackBox object, which holds the set of dropped messages, is equivalent to the Inbox.

A typical implementation of the handleNAckMessages(), which needs to be added to your node implementation if you want to use this feature, looks as following:

public void handleNAckMessages(NackBox nackBox) {
while(nackBox.hasNext()) {
Message msg = nackBox.next();
if(msg instanceof ...) {
\\ handle this type of message
}
}
}

Edges

Nodes are connected by edges. Most projects may be happy with the default edge implementation (which is unidirectional!) or one of the implementations provided in the defaultProject. If you need a more specialized edge, create a subclass from sinalgo.nodes.edges.Edge and put the source file in the nodes/edges/ folder of your project.

Note: The framework only supports one edge type at any time. The type to use can be specified in the configuration file, and it may be switched at runtime through the Preferences menu. Changing the edge type at runtime only affects edges created after the change. It does not replace the already existing edges.

The following edges are already available:
sinalgo.nodes.edges.Edge The default edge implementation, superclass of all edges. This edge is directional. As a result, Sinalgo does not really support bidirectional edges in the sense that there is a single object for a bidirectional edge. The bidirectional edge implementation solves this problem by adding an edge in both directions.
By default, this edge draws itself as a black line between the two end-nodes, and colors itself red when a message is sent over the edge.
sinalgo.nodes.edges.BidirectionalEdge The default bidirectional edge implementation. It ensures that there is an edge in both directions between the two end nodes.
By default, this edge draws itself as a black line between the two end-nodes, and colors itself red when a message is sent over the edge.
projects.defaultProject.nodes
.edges.BooleanEdge
The BooleanEdge extends the default edge implementation with a boolean member flag that may be used arbitrarily. It also carries a static member onlyUseFlagedEdges, which may be used to enable or disable globally the use of the flag.
The provided implementation uses onlyUseFlagedEdges and flag to decide whether the edge is drawn or not: If onlyUseFlagedEdges is true, the edge only draws itself if flag is set to true.
projects.defaultProject.nodes
.edges.BidirectionalBooleanEdge
A bidirectional edge with the features of the boolean edge.
projects.defaultProject.nodes
.edges.GreenEdge
The same as the default edge implementation, but it draws itself as a green line between the two end-nodes.
Edge implementations which are available by default. To manually insert a connection from node u to node v, you may use the method u.addConnectionTo(v). Project sample6 demonstrates how a static network may be built.

Timers

A timer is an object that allows a node to schedule a task in the future. When the task is due, the timer wakes up the node and performs the given task. Any timer object is a subclass of sinalgo.nodes.timers.Timer and implements the method void fire(), which contains the task this timer needs to perform.

To write a project specific timer, implement a subclass of sinalgo.nodes.timers.Timer and put the source file in the nodes/timers/ folder of your project. A timer instance is started by calling either the startAbsolute(double absoluteTime, Node n) method or the startRelative(double relativeTime, Node n) method of the super class. The time specifies when the task should be scheduled, and the node specifies the node on which the task should be executed.

Hint: The default project provides a MessageTimer that schedules to send a message at a given time. The message may be unicast to a specified recipient, or multicast to all immediate neighbors.

Global Timers

A timer object can also be used to perform a task for the simulation framework at a given time. Such a global task is not executing on a particular node, and is suited to perform simulation specific tasks (e.g. add/drop some nodes, select a random node to perform an action, change the network, ...) In synchronous simulation mode, the global timers are handled after the CustomGlobal.preRound() method. In asynchronous simulation mode, a global timer becomes an event that executes when the simulation time reached the execution time of the timer.

To create a global timer, implement a subclass of sinalgo.nodes.timers.Timer just as for the regular node timers. But in contrast to the node related timers, start the timer with its method startGlobalTimer(double relativeTime).

Hint: You may use the same timer implementation as a node-related timer and as a global timer. Just make sure that the fire() method of the timer class does not access the node member when the timer was started as a global timer. This member is set only when the timer is started as a node-related timer.

Customized, Globally Visible Methods: CustomGlobal.java

Each project comes with a CustomGlobal class, which collects global methods specific to the project. This class extends sinalgo.runtime.AbstractCustomGlobal and is located in the root folder of your project. The following table gives an overview of the methods you may overwrite in your project specific CustomGlobal.java. (These methods are defined in the abstract superclass AbstractCustomGlobal.java with an empty body.)
customPaint(...) This paint method is called after the network graph has been drawn. It allows for customizing the drawing of the graph by painting additional information onto the graphics.
handleEmptyEventQueue() The framework calls this method when running in asynchronous mode and there is no event left in the queue. You may generate new events in this method to keep the simulation going.

Note that the batch mode terminates when the event queue is emptied and this method does not insert any new events.

preRun() Called once prior to starting the first round in synchronous mode, or prior to executing the first event in asynchronous mode. Use this method to initialize the simulation.
onExit() Called by the framework before shutting down. To ensure that this method is called in all cases, you should use sinalgo.tools.Tools.exit() to exit, instead of System.exit().
preRound() Called in synchronous mode prior to every round. This method may be suited to perform statistics and write log-files.
postRound() Called in synchronous mode after every round. This method may be suited to perform statistics and write log-files.
checkProjectRequirements() The framework calls this method at startup after having selected a project to check whether the necessary requirements for this project are given. For algorithms that only work correctly in synchronous mode this method check that the user didn't try to execute it in asynchronous mode. If the requirements are not met, you may call sinalgo.tools.Tools.fatalError(String msg) to terminate the application with a fatal error.
nodeAddedEvent(Node n) Called by the framework whenever a node is added to the framework (which is done through the method Runtime.addNode(Node n)). This event may be useful for applications that need to update some graph properties whenever a new node is added (e.g. by the user through the GUI).

Note that this method is also called individually for each node created through the -gen command-line tool, and when the user creates nodes using the GUI menu.

nodeRemovedEvent(Node n) Called by the framework whenever a node is removed from the framework (which is done through the method Runtime.removeNode(Node n)). This event may be useful for applications that need to update some graph properties whenever a node is removed (e.g. by the user through the GUI).

Note that this method is not called when the user removes all nodes using the Runtime.clearAllNodes() method.

Methods you may overwrite in the project owned CustomGlobal class.

Stopping the Simulation

Most importantly for the batch mode, the hasTerminated() method in the CustomGlobal class lets you specify whether a synchronous simulation has reached the final state and the framework may exit. See the calling sequence of the synchronous mode for details on when hasTerminated() is called. In asynchronous simulation, the simulation terminates when all events have been handled. To stop execution prior to handling all events, or at an arbitrary place in synchronous mode, call sinalgo.tools.Tools.exit(). This method executes some cleanup and is preferred to calling System.exit(). In particular, sinalgo.tools.Tools.exit() calls AbstractCustomGlobal.onExit(), which you may overwrite in your project specific CustomGlobal.java file.

Hint: The onExit() method may be a good place to perform final logging steps and project specific cleanup.

Project Specific Drop Down Methods and Buttons

You may extend the GUI with project specific drop down menus and buttons: Add a dummy-method that takes no parameters to your CustomGlobal and implement the desired behavior in its body. There are three different ways to register the method with the GUI:

1) Drop Down Menu Entry: Prefix the method with the annotation @AbstractCustomGlobal.GlobalMethod and specify the menuText. E.g.

@AbstractCustomGlobal.GlobalMethod( menuText="Toggle TC view")
public void myMenuMethod() {
// ...
}

2) Icon Button: Prefix the method with the annotation @AbstractCustomGlobal.CustomButton and specify the imageName and toolTipText. The imageName should be the name of a gif image of size 21x21 pixels, located in the images folder of the project. E.g.

@CustomButton( imageName="myImage.gif", toolTipText="Click me!")
public void myImageButtonMethod() {
// ...
}

3) Text Button: Prefix the method with the annotation @AbstractCustomGlobal.CustomButton and specify the buttonText and toolTipText. E.g.

@CustomButton( buttonText="Clear", toolTipText="Click me!")
public void myTextButtonMethod() {
// ...
}

Image of Menu
Project specific menu
Image of buttons
Project specific buttons

The drop down menu entries (but not the buttons) may be adapted at runtime: Every time the user opens the 'Global' menu, the menu is assembled and includes methods annotated with the GlobalMethod annotation. Before including such a method in the list, the framework calls AbstractCustomGlobal.includeGlobalMethodInMenu(Method m, String defaultText) to allow the project to decide at runtime whether the method should be included or not, and, if necessary, change the default menu text.

Overwrite the method includeGlobalMethodInMenu(Method m, String defaultText) in your project specific CustomGlobal.java file to control the appearance of the 'Global' menu at runtime. The method returns the text to be displayed for each method, or null if the method should not be included.



© Distributed Computing Group
GitHub.com Mark GitHub.com Logo SourceForge.net Logo Valid CSS! Valid HTML 4.01 Transitional