Tutorial 1 - Basic Agent Design and Implementation Introduction This tutorial is based on version 0.3 of the G System. This tutorial will guide you through the process of agent design and implementation. Agents are used to provide behaviour or functionality for elements. The agent interface and the element structure as a whole are defined in the G Core System (GCS) library. All needed information will be provided in this tutorial, but you still might want to take a look at the API documentation while working through the tuorial. When we have created the agents we will also write the application that puts it all together, but we will see that this is a simple step. Step one: sending influences By subclassing the GCS::GAgent class, we already have a compilable agent. Of course it does not do much out of itself, but we only need to implement what we really need. There are two important methods, that the agent interface offers: GAgent::run() and GAgent::receiveInfluence(). The run() method is executed as an independent thread. GAgent inherits QThread to make this possible. The receiveInfluence() method is called whenever an influence is received by the element. GAgent::run() should hold all the code that the agent should do out of itself. It is possible to create any level of complexity here. The agent could even access information from the internet and act accordingly, there is no restriction. Also it is perfectly fine to create more threads, but then starting and parking the agent must be implemented carefully. See the API documentation for details. Our first agent will be called InfluenceSender and inherits GAgent. It should periodically radiate an influence, we can achieve this with a simple reimplementation of GAgent::run(). void InfluenceSender::run() { while(!shutdown) { GEnergy* e = this->requestEnergy(); GElementInfluence influence( this->getElementID(), e->take(0.01)); emit radiateInfluence( influence ); sleep(2); } } Let's walk this through line by line. while(!shutdown) shutdown is a protected attribute of GAgent. By default it is FALSE as long as the agent should execute, if the element should stop to execute then this variable is set to TRUE. With this while loop we make sure the thread stops, that means the run() method returns, when it is supposed to do so. Basically every agent that runs as a thread has this while loop. More complex agents might have to implement their own way of shutting down though. GEnergy* e = this->requestEnergy(); Every element has energy that gives the element a certain characteristic. When we want to influence something, this "personal" characteristic of the element is always included in the influence we generate. GElementInfluence influence( this->getElementID(), e->take(0.01)); The GCS::GElementInfluence class is used as a base class for all influences. It even implements a complete influence in itself without having to create a subclass. The required parameters are the source ID, which is represented by a GCS::GElementID object. This ID of the element can be obtained by GAgent::getElementID(). The second parameter of an influence is the energy that we load the influence with. We already obtained a pointer to the element's energy and GEnergy::take() creates a GEnergy object with the given fraction of the original energy. This means, we take 1% (0.01) of our own energy and load the influence with it. Normally the more energy we put into an influence the stronger is the effect. But we need to consider that every element only has a certain amount of energy so we need to be careful about how much we really use for an influence. As we will see later, by receiving influence the energy amount of the element can increase. emit radiateInfluence( influence ); As soon as we have created the influence object we are ready to send it out. The GAgent class defines two signals for influence sending: GAgent::radiateInfluence() and GAgent::sendInfluence(). The first is used to radiate the influence into the surrounding environment and the second is used to send the influence to a specific destination element. We simply want to radiate the influence into our surrounding. What elements are affected by radiating influences depends on the implementation of the G World Engine (GWE). The implementation we will use in our application is the GWEInterfaceSimple. In this class radiation of influences affects the element's parent, all children of the source element and all elements that are touching the source element (forms overlap). sleep(2); This method is provided by QThread and puts the agent to sleep for given number of seconds. After that execution starts just after this call, which means that we are at the beginning of the while loop. InfluenceSender class declaration We already discussed the implementation details for the InfluenceSender class. Now we will take a brief look at how a subclass of GAgent basically looks. Keep in mind that reimplemented methods should always be declared virtual. #ifndef INFLUENCESENDERH #define INFLUENCESENDERH #include <GAgent.h> class InfluenceSender : public GCS::GAgent { Q_OBJECT public: InfluenceSender(QObject* parent = 0, const char* name = 0); virtual ~InfluenceSender(); protected: virtual void run(); }; #endif To define a GAgent subclass we simply inherit it. Please note that GAgent is in the GCS namespace. Since the using namespace XXX; should be avoided in header files we simply put the namespace in front of used classes. Also every subclass should have a virtual destructor as this allows clean freeing of memory. This especially applies for custom agents and forms. Step two: receiving influences Now that we have seen how to send an influence in the run() method we can take a look at how we handle influence receiving in the receiveInfluence() slot. This slot gets called whenever the element receives an influence, just like the one we have created in the previous section. We will stick to a very simple implementation so it is easy to follow. The agent we use for receiving will be called InfluenceReceiver, and all we want to do is to print information about the received influence. So this is how the implementation looks: void InfluenceReceiver::receiveInfluence(GElementInfluence& influence) { const GElementID& source_id = influence.source(); qDebug("influence received from " + QString::number(source_id.getID()) + " by " + QString::number(getElementID().getID()) + "..."); GEnergy& inf_energy = influence.Energy; qDebug(" energy (level,amount,sigma): " + QString::number(inf_energy.level()) + " " + QString::number(inf_energy.amount()) + " " + QString::number(inf_energy.sigma())); GEnergy* own_energy = this->requestEnergy(); own_energy->put(inf_energy); } This implementation shows how to access the data of an received influence. We will also write this data to the console so we can see what our elements are doing when we run the application. qDebug() is used for output and simply accepts a string which it writes to the standard output of the application which in turn is our console. QString::number() converts a given number to a string. const GElementID& source_id = influence.source(); GElementInfluence::source() returns a constant GElementID reference to the source element. With it we can identify from where the influence came. In complexer environments the receiving agent could create a child (subelement) with a connection ID of the source element's GElementID. A connection ID of an element can be seen as the element this element is concerned with. In particular it could send (in contrast to radiate!) influences of certain specialised types to it to achieve some desired result. See the API documentation of GCS::GObject::Connection for additional information. This attribute will play an important role in complex settings with intelligent agents. GEnergy& inf_energy = influence.Energy; The energy of an influence is a publicly accessible attribute. This essentially means that we can even modify it. Here we store a reference to it in a local variable. GEnergy* own_energy = this->requestEnergy(); own_energy->put(inf_energy); And this is exactly what we do here. We "put" the energy from the influence into the element's own energy. This is a process of mixing two energies of different level, amount and sigma into one energy. This is perfectly comparable to mixing two bottles of water with different colours (the colour being the energy level). So we see that the GCS::GElementInfluence class offers two pieces of information: The ID of the influence source. The energy that the influence is loaded with. Step three: writing the application After writing our two agents, we will put it all together in a small application. For this we will create two elements, a parent and its child. Radiated influences always influence the source's parent and children, so in this setting the elements will influence each other. The main() function could look like this: #include <qapplication.h> #include "InfluenceSender.h" #include "InfluenceReceiver.h" #include <GWEInterfaceSimple.h> #include <GElement.h> #include <GObject.h> #include <GEnergy.h> #include <GElementID.h> using namespace GCS; using namespace GWE; int main(int argc, char** argv) { QApplication a(argc,argv); GWEInterfaceSimple* gwe = new GWEInterfaceSimple(); GObject* object_parent = new GObject( new GEnergy(5,10,3), //level,amount,sigma NULL, //no form GElementID(1), //the element is it's own parent GElementID(1), //ID is 1 GElementID(1)); //connection ID is 1 - itself GElement* element_parent = new GElement(object_parent); element_parent->addAgent(new InfluenceSender()); element_parent->addAgent(new InfluenceReceiver()); gwe->add(element_parent); GElement* child = new GElement( new GObject( new GEnergy(3,1,2), //level,amount,sigma NULL, //no form GElementID(1), //parent GElementID(2), //ID is 2 GElementID(1))); //connection ID is 1 - the parent child->addAgent(new InfluenceSender()); child->addAgent(new InfluenceReceiver()); object_parent->addChild(child->getElementID()); gwe->add(child); element_parent->executeElement(); child->executeElement(); return a.exec(); } The main() function is in general very flexible in design. It is important that a QApplication object is present before any G related classes are instantiated, because many of these classes are derived from QObject. To be able to create a QApplication instance we need to include qapplication.h. Additionally we include the two header files from our self-made agents. Also it is important to include every used class of the G libraries. And in implementation files, using namespace XXX; should be used so we can use these classes without namespaces. After creating the QApplication, we can create a GWE instance. This is used to handle all elements and manage influence sending and radiating between elements. Without a GWE influences wouldn't work. The next step is to create the GObject of the parent element. The object holds all the data of an element, so we have to give an energy, a form and IDs for the parent, for itself and for the connected element. We store the created object in a pointer as we need it later to add the child. Creating the element itself is then very simple, we only need to pass it the object. In order to set agents for an element we can use GElement::addAgent(). The element takes care of initialisation of the agent, which means setting the GAgent::Object and GAgent::Agents attributes and starting the agent thread when the element is executed. After the element itself is created and the agents are added, we add the element to the GWE. Note that when an agent creates a child element it only needs to emit a GAgent::childElementCreated() signal and the GWE recognises the child automatically, as long as the parent itself was added to the GWE. This means that only the top-most element needs to be added to the GWE manually and if this top-most element creates it's own children they will be automatically registered with the GWE through this signal. In our tutorial we don't create the child with an agent in the parent, so we will also have to do the parenting and the registration with the GWE ourselves. It is important that every element has a parent. The top-most element is essentially its own parent. This is currently the convention used by the GWE, but it might change in the future. The API Reference is your friend in this case. Then we create the child element with a slightly different energy level. After adding the agents we also add this element as a child to the parent's object. Then we add to the GWE. As soon as all initialisation is done we can execute the elements and start the Qt event loop. Our application is running now. When an element is executed, it also starts all agent threads, just as it parks all agents when the element is parked. So no direct communication to the agents is necessary. You could now try to complete the source code yourself with missing header files and implementations and compile it. It is highly recommended you go for it yourself. If you do encounter problems you cannot solve, you are welcome to use the forums on our website or the mailing list for your questions. You could also download the source files for this tutorial, which includes a qmake project file and short installation instructions, but this is only recommended when you don't manage to create the application yourself. Source Files The complete source code for the tutorial can be found at ftp://ftp.g-system.at/data/tutorials/.