/***************************************************************************
* Copyright (C) 2003-2004 by Raphael Langerhorst *
* raphael-langerhorst@gmx.at *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
#ifndef GAGENTH
#define GAGENTH
#include <qobject.h> //inherited by GAgent
#include <qthread.h> //inherited by GAgent
#include <qptrlist.h> //@todo remove when moving to Qt4
#include <qvaluelist.h>
class QDomElement;
namespace GCS
{
class GElement;
class GObject;
class GEnergy;
class GForm;
class GVector3;
class GMatrix44;
class GElementID;
class GElementInfluence;
/**
\class GAgent GAgent.h
\brief Defines the behaviour of an element
@author Raphael Langerhorst
Agents are used to give the element functionality or life.
All actions of the element come from its agents,
even all laws (gravity, ...) that the element adhers to
are implemented by agents;
To implement an agent this class must be inherited and all
desired abstract methods have to be implemented. In particular
GAgent::run() and GAgent::receiveInfluence() are of interest.
Additionally the virtual methods for parking and resuming
execution can be used for various purposes. Especially threadStart()
can be used to react to the time passed between parking the agent
and resuming execution.
Implementing agents directly on top of this class is of course
the most powerful way for agent implementation. But it could
be a wise idea to create another agent interface layer that is
for example capable of scripting and runtime code morphing.
Signals that represent data changes such as energyChanged()
have to be emitted by the agent on purpose, they are not automated.
This gives greater control over message broadcasting and
thus it is easier to minimize overhead when the agent makes
multiple changes. But this also means more responsibility for
the agent developer.
@todo Changed signals such as energyChanged() should also be received
@todo by the agent since other agents could emit these and it should
@todo be possible to react to these events.
@todo signal for reparenting
*/
class GAgent : public QObject, protected QThread
{
//make this class a Q_OBJECT, which means that signals
//and slots can be used (see Qt documentation)
Q_OBJECT
private:
/**
* This is a pointer to the element's object;
* all changes to the element are done by accessing
* the object through this pointer;
* all requestXXXX() methods use it;
* @note When an agent was never added to an element
* this pointer is NULL!! Such agents should never
* be executed on their own.
* @see GElement::addAgent()
*/
GObject* Object;
/**
* This list stores all agents that are in the
* same element. Since it is not clean code to
* change other agents directly they are
* accessible read only.
* @note When an agent was never added to an element
* this pointer is NULL!! Such agents should never
* be executed on their own.
* @see GElement::addAgent()
*/
QValueList<GAgent*>* Agents;
protected:
/**
* The default implementation of beginPark() sets this value to
* true. It can be used inside the run() method to check if the
* agent should park (this is done by returning from the run method).
*
* A standard run() method would look like this:
* @code void CustomAgent::run()
* {
* while(!shutdown)
* {
* //do something
* msleep(breaktime); //take a nap (very important)
* }
* }
* @endcode
*
*/
bool shutdown;
public:
/**
* The constructor does not need any kind of parameter.
* The optional parameters are forwarded to the
* constructor of QObject.
*/
GAgent(QObject* parent = 0, const char* name = 0);
/**
* The virtual destructor makes sure that inherited classes
* are cleanly destroyed when only a pointer to GAgent is
* used for freeing memory.
* Please make sure that all subclasses of GAgent have a
* virtual destructor.
*/
virtual ~GAgent() {}
/**
* This is public because it can be useful to look to which
* element this agent belongs (used by GWE to process agentChanged()
* signals)
* @return the element's ID (const reference)
*/
const GElementID& getElementID() const;
protected:
/**
* When agents are started this code is executed as a thread.
* This is where the agent's functionality is defined.
* @see GAgent::receiveInfluence
* @see GAgent::shutdown
*/
virtual void run();
/**
* @return the element's object
*/
GObject* requestObject();
/**
* @return the element's energy
*/
GEnergy* requestEnergy();
/**
* @return the element's form
*/
GForm* requestForm();
/**
* @return the element's agents as a list (const reference)
*/
QValueList<const GAgent*> requestAgents() const;
/**
* @return the element's connection ID (const reference)
*/
const GElementID& getConnectionID() const;
//BEGIN Helper member functions for working with ElementData
protected:
/**
* If an element with given tag name does not yet exist, it is created!
* Sets ok to false if data could not be read.
* @return the first child of the ElementData XML document that matches given tag name
*/
QDomElement xmlGetTopElement(QString tag_name, bool& ok);
/**
* If an element with given tag name does not yet exist, it is created!
* In case the child node did already exist, existed is set to TRUE, otherwise FALSE;
* @return the first child element of given XML element that matches given tag name
*/
QDomElement xmlGetElement(QDomElement parent, QString child_tag_name, bool& existed);
/**
* If an element with given tag name does not yet exist, it is created!
* In case the child node did already exist, existed is set to TRUE, otherwise FALSE;
*
* Imagine you have element data like this:
*
* <dynamics>
* <translationspeed>
* <x>3</x>
* <y>-2</y>
* <z>1</z>
* </translationspeed>
* <rotationspeed>
* <x>5</x>
* <y>1</y>
* <z>-7</z>
* </rotationspeed>
* </dynamics>
*
* @code
* xmlGetElement("/dynamics/translationspeed/z",existed);
* @endcode
* gives you the <z>1</z> QDomElement.
*
* @return the first element that matches given xpath
*/
QDomElement xmlGetElement(QString xpath, bool& existed);
/**
* Rmoves all children from given node, this is
* needed if you want to replace the body of
* a QDomElement.
*
* This is used by all xmlSet* methods, so you
* should use this as well if you are updating your
* own data.
* Be aware that THIS also removes all attributes!
*/
void xmlRemoveNodeContent(QDomElement node);
/**
* Sets ok to false if flag could not be read.
* @return the node value of given element as bool value.
*/
bool xmlGetFlag(QDomElement element, bool& ok);
/**
* Sets ok to false if flag could not be read.
* @return the node value of given xpath as bool value.
*/
bool xmlGetFlag(QString xpath, bool& ok);
/**
* Sets ok to false if flag could not be set.
*/
void xmlSetFlag(QDomElement element, bool value, bool& ok);
/**
* Sets ok to false if flag could not be set.
*/
void xmlSetFlag(QString xpath, bool value, bool& ok);
/**
* Sets ok to false if integer data could not be read.
* @return the node value of given element as integer.
*/
int xmlGetInteger(QDomElement element, bool& ok);
/**
* Sets ok to false if integer data could not be read.
* @return the node value of given xpath as integer.
*/
int xmlGetInteger(QString xpath, bool& ok);
/**
* Sets ok to false if integer data could not be set.
*/
void xmlSetInteger(QDomElement element, int value, bool& ok);
/**
* Sets ok to false if integer data could not be set.
*/
void xmlSetInteger(QString xpath, int value, bool& ok);
/**
* Sets ok to false if integer data could not be read.
* @return the node value of given element as integer.
*/
unsigned long xmlGetULongInteger(QDomElement element, bool& ok);
/**
* Sets ok to false if integer data could not be read.
* @return the node value of given xpath as integer.
*/
unsigned long xmlGetULongInteger(QString xpath, bool& ok);
/**
* Sets ok to false if integer data could not be set.
*/
void xmlSetULongInteger(QDomElement element, unsigned long value, bool& ok);
/**
* Sets ok to false if integer data could not be set.
*/
void xmlSetULongInteger(QString xpath, unsigned long value, bool& ok);
/**
* Sets ok to false if integer data could not be read.
* @return the node value of given element as double value (floating point).
*/
double xmlGetDouble(QDomElement element, bool& ok);
/**
* Sets ok to false if integer data could not be read.
* @return the node value of given xpath as double value (floating point).
*/
double xmlGetDouble(QString xpath, bool& ok);
/**
* Sets ok to false if double data could not be set.
*/
void xmlSetDouble(QDomElement element, double value, bool& ok);
/**
* Sets ok to false if double data could not be set.
*/
void xmlSetDouble(QString xpath, double value, bool& ok);
/**
* Sets ok to false if vector data could not be read or if vector did not exist.
* @return vector value from given element
*/
GVector3 xmlGetVector3(QDomElement element, bool& ok);
/**
* Gives you the vector stored in the given path, like if you have:
*
* <dynamics>
* <translationspeed>
* <x>3</x>
* <y>-2</y>
* <z>1</z>
* </translationspeed>
* <rotationspeed>
* <x>5</x>
* <y>1</y>
* <z>-7</z>
* </rotationspeed>
* </dynamics>
*
* And you call xmlGetVector3("/dynamics/rotationspeed",ok);
* this gives you a vector with (5,1,-7).
*
* ok is set to false if something failed.
*/
GVector3 xmlGetVector3(QString xpath, bool& ok);
/**
* Stores given vector in given XML element.
*/
void xmlSetVector3(QDomElement element, const GVector3& value, bool& ok);
/**
* Sets given vector into given xpath, if xpath does not exist, it
* is created, see above for an example.
*/
void xmlSetVector3(QString xpath, const GVector3& value, bool& ok);
//END Helper member functions for working with ElementData
protected slots:
/**
* Called when the agent should start to park itself.
* An Agent is considered to be parked when the thread
* ceases to execute.
* By default, this method sets shutdown to true
* and returns immediately.
* @see shutdown
*/
virtual void beginPark();
/**
* Causes the internal thread to start executing run().
* This is used by GElement to start the agent.
* @see beginPark();
*/
virtual void threadStart(double seconds_elapsed);
public slots:
/**
* When an influence reaches an element it is forwarded to
* every agent of that element through this slot.
* All influence handling should go here.
* @note Please be aware that receiveInfluence
* and all agent threads of the element, GAgent::run(), might
* be executed in parallel! Consider using locking mechanisms
* for the element's form, energy and object. All of them
* inherit QMutex, use it. Also consider using QMutexLocker
* because it makes locking quite easy.
*/
virtual void receiveInfluence(const GCS::GElementInfluence&);
/**
* @see GElement::reparent()
* @note transformation is already applied to the form!!!
*/
virtual void reparented(const GCS::GElementID& old_parent, const GCS::GElementID& new_parent, const GCS::GMatrix44& transformation);
signals:
/**
* This signal is used to send influences to
* a specific destination element.
*/
void sendInfluence(const GCS::GElementID& target,
const GCS::GElementInfluence& influence);
/**
* If an influence is not supposed to be sent to a specific
* target, then it can also be radiated with this signal.
* It depends on the implementation in the GWE which elements
* receive this influence. Currently this includes the
* parent, all direct children and all elements that are
* in contact with this element (in terms of location and form).
* But this always depends on the GWE in use, please check
* the relevant documentation.
* @see GWE::GWEInterface
*/
void radiateInfluence(const GCS::GElementInfluence& influence);
/**
* Emitted when the element's energy has changed.
* @note not automated!
*/
void energyChanged(const GCS::GEnergy& changedEnergy);
/**
* Emitted when the element's form has changed.
* @note not automated!
*/
void formChanged(const GCS::GForm& changedForm);
/**
* Emitted when the agent changed it's state/data.
* @note not automated!
*/
void agentChanged(const GCS::GAgent& changedAgent);
/**
* Emitted when a child element has been created.
* @note: if you create a child and don't emit this
* signal it is NOT guranteed that your element get's
* recognized by the GWE.
*/
void childElementCreated(GCS::GElement* newElement);
/**
* Emitted when a child element was removed (deleted).
* note: This signal should NOT be emitted when the child
* just changed its parent. The GWE is responsible for
* removing the specified element from memory.
*/
void childElementRemoved(const GCS::GElementID& childID);
public:
/**
* Tells whether the agent is currently executing or not.
* @note QThread is executing run() as a thread!
* If no additional threads are created then there is no need
* to reimplement this method because the current
* implementation will check QThread::running()
*
* @note If you reimplement this method be sure to call
* the base method with GAgent::isParked() and immediately
* return false if this call returns false:
*
* @code
* bool CustomAgent::isParked()
* {
* if (!GAgent::isParked())
* return false;
* //check other threads that belong to this agent
* }
* @endcode
*
* Normally you don't need to reimplement this but it might be
* possible that a subclass of GAgent uses several threads and then
* a simple check of the agent's main thread wouldn't be enough.
*/
virtual bool isParked() const;
/**
* GElement needs to set private properties and connect to protected slots.
*/
friend class GElement;
};
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1