/* This file is part of the TDE games library Copyright (C) 2001 Andreas Beckermann (b_mann@gmx.de) Copyright (C) 2001 Martin Heni (martin@heni-online.de) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KGAMEPROPERTY_H_ #define __KGAMEPROPERTY_H_ #include #include #include #include class KGame; class KPlayer; class KGamePropertyHandler; using namespace std; /** * @short Base class of KGameProperty * * The KGamePropertyBase class is the base class of KGameProperty. See * KGameProperty for further information. * * @author Andreas Beckermann **/ class TDE_EXPORT KGamePropertyBase { public: enum PropertyDataIds { // these belong to KPlayer/KGame! //KPlayer IdGroup=1, IdUserId=2, IdAsyncInput=3, IdTurn=4, IdName=5, //KGame IdGameStatus=6, IdMaxPlayer=7, IdMinPlayer=8, // Input Grabbing IdGrabInput=16, IdReleaseInput=17, IdCommand, // Reserved for internal use IdUser=256, IdAutomatic=0x7000 // Id's from here on are automatically given (16bit) }; /** * Commands for advanced properties (TQ_INT8) **/ enum PropertyCommandIds { // General CmdLock=1, // Array CmdAt=51, CmdResize=52, CmdFill=53, CmdSort=54, // List (could be the same id's actually) CmdInsert=61, CmdAppend=62, CmdRemove=63, CmdClear=64 }; /** * The policy of the property. This can be PolicyClean (setValue uses * send), PolicyDirty (setValue uses changeValue) or * PolicyLocal (setValue uses setLocal). * * A "clean" policy means that the property is always the same on every * client. This is achieved by calling send which actually changes * the value only when the message from the MessageServer is received. * * A "dirty" policy means that as soon as setValue is called the * property is changed immediately. And additionally sent over network. * This can sometimes lead to bugs as the other clients do not * immediately have the same value. For more information see * changeValue. * * PolicyLocal means that a KGameProperty behaves like ever * "normal" variable. Whenever setValue is called (e.g. using "=") * the value of the property is changes immediately without sending it * over network. You might want to use this if you are sure that all * clients set the property at the same time. **/ enum PropertyPolicy { PolicyUndefined = 0, PolicyClean = 1, PolicyDirty = 2, PolicyLocal = 3 }; /** * Constructs a KGamePropertyBase object and calls registerData. * @param id The id of this property. MUST be UNIQUE! Used to send and * receive changes in the property of the playere automatically via * network. * @param owner The owner of the object. Must be a KGamePropertyHandler which manages * the changes made to this object, i.e. which will send the new data **/ KGamePropertyBase(int id, KGamePropertyHandler* owner); KGamePropertyBase(int id, KGame* parent); KGamePropertyBase(int id, KPlayer* parent); /** * Creates a KGamePropertyBase object without an owner. Remember to call * registerData! **/ KGamePropertyBase(); virtual ~KGamePropertyBase(); /** * Changes the consistency policy of a property. The * PropertyPolicy is one of PolicyClean (defaulz), PolicyDirty or PolicyLocal. * * It is up to you to decide how you want to work. **/ void setPolicy(PropertyPolicy p) { mFlags.bits.policy = p; } /** * @return The default policy of the property **/ PropertyPolicy policy() const { return (PropertyPolicy)mFlags.bits.policy; } /** * Sets this property to emit a signal on value changed. * As the proerties do not inehrit TQObject for optimisation * this signal is emited via the KPlayer or KGame object **/ void setEmittingSignal(bool p) { mFlags.bits.emitsignal=p; } /** * See also setEmittingSignal * @return Whether this property emits a signal on value change **/ bool isEmittingSignal() const { return mFlags.bits.emitsignal; } /** * Sets this property to try to optimize signal and network handling * by not sending it out when the property value is not changed. **/ void setOptimized(bool p) { mFlags.bits.optimize = p ; } /** * See also setOptimize * @return Whether the property optimizes access (signals,network traffic) **/ bool isOptimized() const { return mFlags.bits.optimize; } /** * @return Whether this property is "dirty". See also setDirty **/ bool isDirty() const { return mFlags.bits.dirty; } /** * A locked property can only be changed by the player who has set the * lock. See also setLocked * @return Whether this property is currently locked. **/ bool isLocked() const { return mFlags.bits.locked; } /** * A locked property can only be changed by the player who has set the * lock. * * You can only call this if isLocked is false. A message is sent * over network so that the property is locked for all players except * you. * * @return returns false if the property can not be locked, i.e. it is already locked * **/ bool lock(); /** * A locked property can only be changed by the player who has set the * lock. * * You can only call this if isLocked is false. A message is sent * over network so that the property is locked for all players except * you. * * @return returns false if the property can not be locked, i.e. it is already locked * **/ bool unlock(bool force=false); /** * This will read the value of this property from the stream. You MUST * overwrite this method in order to use this class * @param s The stream to read from **/ virtual void load(TQDataStream& s) = 0; /** * Write the value into a stream. MUST be overwritten **/ virtual void save(TQDataStream& s) = 0; /** * send a command to advanced properties like arrays * @param stream The stream containing the data of the comand * @param msgid The ID of the command - see PropertyCommandIds * @param isSender whether this client is also the sender of the command **/ virtual void command(TQDataStream &stream, int msgid, bool isSender=false); /** * @return The id of this property **/ int id() const { return mId; } /** * @return a type_info of the data this property contains. This is used * e.g. by KGameDebugDialog **/ virtual const type_info* typeinfo() { return &typeid(this); } /** * You have to register a KGamePropertyBase before you can use it. * * You MUST call this before you can use KGamePropertyBase! * * @param id the id of this KGamePropertyBase object. The id MUST be * unique, i.e. you cannot have two properties with the same id for one * player, although (currently) nothing prevents you from doing so. But * you will get strange results! * * @param owner The owner of this data. This will send the data * using KPropertyHandler::sendProperty whenever you call send * * @param p If not 0 you can set the policy of the property here * * @param name if not 0 you can assign a name to this property * **/ int registerData(int id, KGamePropertyHandler* owner,PropertyPolicy p, TQString name=0); /** * This is an overloaded member function, provided for convenience. * It differs from the above function only in what argument(s) it accepts. **/ int registerData(int id, KGamePropertyHandler* owner, TQString name=0); /** * This is an overloaded member function, provided for convenience. * It differs from the above function only in what argument(s) it accepts. **/ int registerData(int id, KGame* owner, TQString name=0); /** * This is an overloaded member function, provided for convenience. * It differs from the above function only in what argument(s) it accepts. **/ int registerData(int id, KPlayer* owner, TQString name=0); /** * This is an overloaded member function, provided for convenience. * It differs from the above function only in what argument(s) it accepts. * In particular you can use this function to create properties which * will have an automatic id assigned. The new id is returned. **/ int registerData(KGamePropertyHandler* owner,PropertyPolicy p=PolicyUndefined, TQString name=0); void unregisterData(); protected: /** * A locked property can only be changed by the player who has set the * lock. * * You can only call this if isLocked is false. A message is sent * over network so that the property is locked for all players except * you. * Usually you use lock and unlock to access this property * **/ void setLock(bool l); /** * Sets the "dirty" flag of the property. If a property is "dirty" i.e. * KGameProperty::setLocal has been called there is no guarantee * that all clients share the same value. You have to ensure this * yourself e.g. by calling KGameProperty::setLocal on every * client. You can also ignore the dirty flag and continue working withe * the property depending on your situation. **/ void setDirty(bool d) { mFlags.bits.dirty = d ; } /** * Forward the data to the owner of this property which then sends it * over network. save is used to store the data into a stream so * you have to make sure that function is working properly if you * implement your own property! * * Note: this sends the current property! * * Might be obsolete - KGamePropertyArray still uses it. Is this a bug * or correct? **/ bool sendProperty(); /** * Forward the data to the owner of this property which then sends it * over network. save is used to store the data into a stream so * you have to make sure that function is working properly if you * implement your own property! * * This function is used by send to send the data over network. * This does not send the current value but the explicitly * given value. * * @return TRUE if the message could be sent successfully, otherwise * FALSE **/ bool sendProperty(const TQByteArray& b); /** * Causes the parent object to emit a signal on value change **/ void emitSignal(); protected: KGamePropertyHandler* mOwner; // Having this as a union of the bitfield and the char // allows us to stream this quantity easily (if we need to) // At the moment it is not yet transmitted union Flags { char flag; struct { // unsigned char dosave : 1; // do save this property // unsigned char delaytransmit : 1; // do not send immediately on // change but a KPlayer:TQTimer // sends it later on - fast // changing variables unsigned char emitsignal : 1; // KPlayer notifies on variable change (true) //unsigned char readonly : 1; // whether the property can be changed (false) unsigned char optimize : 1; // whether the property tries to optimize send/emit (false) unsigned char dirty: 1; // whether the property dirty (setLocal() was used) unsigned char policy : 2; // whether the property is always consistent (see PropertyPolicy) unsigned char locked: 1; // whether the property is locked (true) } bits; } mFlags; private: friend class KGamePropertyHandler; void init(); private: int mId; }; /** * @short A class for network transparent games * * Note: The entire API documentation is obsolete! * * The class KGameProperty can store any form of data and will transmit it via * network whenver you call send. This makes network transparent games * very easy. You first have to register the data to a KGamePropertyHandler * using KGamePropertyBase::registerData (which is called by the * constructor). For the KGamePropertyHandler you can use * KGame::dataHandler or KPlayer::dataHandler but you can also create your * own data handler. * * There are several concepts you can follow when writing network games. These * concepts differ completely from the way how data is transferred so you should * decide which one to use. You can also mix these concepts for a single * property but we do not recommend this. The concepts: *
    *
  • Always Consistent (clean) *
  • Not Always Consistent (dirty) *
  • A Mixture (very dirty) *
* I repeat: we do not recommend the third option ("a mixture"). Unless * you have a good reason for this you will probably introduce some hard to find * (and to fix) bugs. * * @section Always consistent (clean): * * This "policy" is default. Whenever you create a KGameProperty it is always * consistent. This means that consistency is the most important thing for the * property. This is achieved by using send to change the value of the * property. send needs a running KMessageServer and therefore * MUST be plugged into a KGamePropertyHandler using either * registerData or the constructor. The parent of the dataHandler must be able * to send messages (see above: the message server must be running). If you use * send to change the value of a property you won't see the effect * immediately: The new value is first transferred to the message server which * queues the message. As soon as all messages in the message server * which are before the changed property have been transferred the message * server delivers the new value of the KGameProperty to all clients. A * TQTimer::singleShot is used to queue the messages inside the * KMessageServer. * * This means that if you do the following: * \code * KGamePropertyInt myProperty(id, dataHandler()); * myProperty.initData(0); * myProperty = 10; * int value = myProperty.value(); * \endcode * then "value" will be "0". initData is used to initialize the property * (e.g. when the KMessageServer is not yet running or can not yet be * reached). This is because "myProperty = 10" or "myProperty.send(10)" send a * message to the KMessageServer which uses TQTimer::singleShot to * queue the message. The game first has to go back into the event loop where * the message is received. The KGamePropertyHandler receives the new value * sets the property. So if you need the new value you need to store it in a * different variable (see setLocal which creates one for you until the * message is received). The KGamePropertyHandler emits a signal (unless * you called setEmitSignal with false) when the new value is received: * KGamePropertyHandler::signalPropertyChanged. You can use this to react * to a changed property. * * This may look quite confusing but it has a big advantage: all * KGameProperty objects are ensured to have the same value on all clients in * the game at every time. This way you will save you a lot of trouble as * debugging can be very difficult if the value of a property changes * immediately on client A but only after one or two additianal messages * (function calls, status changes, ...) on client B. * * The only disadvantage of this (clean) concept is that you cannot use a * changed variable immediately but have to wait for the KMessageServer to * change it. You probably want to use * KGamePropertyHandler::signalPropertyChanged for this. * * @section Not Always Consistent (dirty): * * There are a lot of people who don't want to use the (sometimes quite complex) * "clean" way. You can use setAlwaysConsistent to change the default * behaviour of the KGameProperty. If a property is not always consistent * it will use changeValue to send the property. changeValue also uses * send to send the new value over network but it also uses * setLocal to create a local copy of the property. This copy is created * dynamically and is deleted again as soon as the next message from the network * is received. To use the example above again: * \code * KGamePropertyInt myProperty(id, dataHandler()); * myProperty.setAlwaysConsistent(false); * myProperty.initData(0); * myProperty = 10; * int value = myProperty.value(); * \endcode * Now this example will "work" so value now is 10. Additionally the * KMessageServer receives a message from the local client (just as explained * above in "Always Consistent"). As soon as the message returns to the local * client again the local value is deleted, as the "network value" has the same * value as the local one. So you won't lose the ability to use the always * consistent "clean" value of the property if you use the "dirty" way. Just use * networkValue to access the value which is consistent among all clients. * * The advantage of this concept is clear: you can use a KGameProperty as * every other variable as the changes value takes immediate effect. * Additionally you can be sure that the value is transferred to all clients. * You will usually not experience serious bugs just because you use the "dirty" * way. Several events have to happen at once to get these "strange errors" * which result in inconsistent properties (like "game running" on client A but * "game ended/paused" on client B). But note that there is a very good reason * for the existence of these different concepts of KGameProperty. I have * myself experienced such a "strange error" and it took me several days to find * the reason until I could fix it. So I personally recommend the "clean" way. * On the other hand if you want to port a non-network game to a network game * you will probably start with "dirty" properties as it is you will not have to * change that much code... * * @section A Mixture (very dirty): * * You can also mix the concepts above. Note that we really don't recommend * this. With a mixture I mean something like this: * \code * KGamePropertyInt myProperty(id, dataHandler()); * myProperty.setAlwaysConsistent(false); * myProperty.initData(0); * myProperty = 10; * myProperty.setAlwaysConsistent(true); * myProperty = 20; * \endcode * (totally senseless example, btw) I.e. I am speaking of mixing both concepts * for a single property. Things like * \code * KGamePropertyInt myProperty1(id1, dataHandler()); * KGamePropertyInt myProperty2(id2, dataHandler()); * myProperty1.initData(0); * myProperty2.initData(0); * myProperty1.setAlwaysConsistent(false); * myProperty2.setAlwaysConsistent(true); * myProperty1 = 10; * myProperty2 = 20; * \endcode * are ok. But mixing the concepts for a single property will make it nearly * impossible to you to debug your game. * * So the right thing to do(tm) is to decide in the constructor whether you want * a "clean" or "dirty" property. * * Even if you have decided for one of the concepts you still can manually * follow another concept than the "policy" of your property. So if you use an * always consistent KGameProperty you still can manually call * changeValue as if it was not always consistent. Note that although this is * also kind of a "mixture" as described above this is very useful sometimes. In * contrast to the "mixture" above you don't have the problem that you don't * exactly know which concept you are currently following because you used the * function of the other concept only once. * * @section Custom classes: * * If you want to use a custum class with KGameProperty you have to implement the * operators << and >> for TQDataStream: * \code * class Card * { * public: * int type; * int suite; * }; * TQDataStream& operator<<(TQDataStream& stream, Card& card) * { * TQ_INT16 type = card.type; * TQ_INT16 suite = card.suite; * s << type; * s << suite; * return s; * } * TQDataStream& operator>>(TQDataStream& stream, Card& card) * { * TQ_INT16 type; * TQ_INT16 suite; * s >> type; * s >> suite; * card.type = (int)type; * card.suite = (int)suite; * return s; * } * * class Player : KPlayer * { * [...] * KGameProperty mCards; * }; * \endcode * * Note: unlike most QT classes KGameProperty objects are *not* deleted * automatically! So if you create an object using e.g. KGameProperty* data = * new KGameProperty(id, dataHandler()) you have to put a delete data into your * destructor! * * @author Andreas Beckermann **/ template class KGameProperty : public KGamePropertyBase { public: /** * Constructs a KGameProperty object. A KGameProperty object will transmit * any changes to the KMessageServer and then to all clients in the * game (including the one that has sent the new value) * @param id The id of this property. MUST be UNIQUE! Used to send and * receive changes in the property of the playere automatically via * network. * @param owner The parent of the object. Must be a KGame which manages * the changes made to this object, i.e. which will send the new data. * Note that in contrast to most KDE/QT classes KGameProperty objects * are not deleted automatically! **/ // TODO: ID: Very ugly - better use something like parent()->propertyId() or so which assigns a free id automatically. KGameProperty(int id, KGamePropertyHandler* owner) : KGamePropertyBase(id, owner) { init(); } /** * This constructor does nothing. You have to call * KGamePropertyBase::registerData * yourself before using the KGameProperty object. **/ KGameProperty() : KGamePropertyBase() { init(); } virtual ~KGameProperty() {} /** * Set the value depending on the current policy (see * setConsistent). By default KGameProperty just uses send to set * the value of a property. This behaviour can be changed by using * setConsistent. * @param v The new value of the property **/ void setValue(type v) { switch (policy()) { case PolicyClean: send(v); break; case PolicyDirty: changeValue(v); break; case PolicyLocal: setLocal(v); break; default: // NEVER! kdError(11001) << "Undefined Policy in property " << id() << endl; return; } } /** * This function sends a new value over network. * * Note that the value DOES NOT change when you call this function. This * function saves the value into a TQDataStream and calls * sendProperty where it gets forwarded to the owner and finally the * value is sent over network. The KMessageServer now sends the * value to ALL clients - even the one who called this function. As soon * as the value from the message server is received load is called * and _then_ the value of the KGameProperty has been set. * * This ensures that a KGameProperty has _always_ the same value on * _every_ client in the network. Note that this means you can NOT do * something like * \code * myProperty.send(1); * doSomething(myProperty); * \endcode * as myProperty has not yet been set when doSomething is being called. * * You are informed about a value change by a singal from the parent of * the property which can be deactivated by setEmittingSignal because of * performance (you probably don't have to deactivate it - except you * want to write a real-time game like Command&Conquer with a lot of * acitvity). See emitSignal * * Note that if there is no KMessageServer accessible - before * the property has been registered to the KGamePropertyHandler (as * it is the case e.g. before a KPlayer has been plugged into the * KGame object) the property is *not* sent but set *locally* (see * setLocal)! * * @param v The new value of the property * @return whether the property could be sent successfully * @see setValue setLocal changeValue value **/ bool send(type v) { if (isOptimized() && mData == v) { return true; } if (isLocked()) { return false; } TQByteArray b; TQDataStream stream(b, IO_WriteOnly); stream << v; if (!sendProperty(b)) { setLocal(v); return false; } return true; } /** * This function sets the value of the property directly, i.e. it * doesn't send it to the network. * * Int contrast to @see you change _only_ the local value when using * this function. You do _not_ change the value of any other client. You * probably don't want to use this if you are using a dedicated server * (which is the only "client" which is allowed to change a value) but * rather want to use send(). * * But if you use your clients as servers (i.e. all clients receive a * players turn and then calculate the reaction of the game theirselves) * then you probably want to use setLocal as you can do things like * \code * myProperty.setLocal(1); * doSomething(myProperty); * \endcode * on every client. * * If you want to set the value locally AND send it over network you * want to call changeValue! * * You can also use setPolicy to set the default policy to * PolicyLocal. * * @see setValue send changeValue value **/ bool setLocal(type v) { if (isOptimized() && mData == v) { return false; } if (isLocked()) { return false; } mData = v; setDirty(true); if (isEmittingSignal()) { emitSignal(); } return true; } /** * This function does both, change the local value and change the * network value. The value is sent over network first, then changed * locally. * * This function is a convenience function and just calls send * followed by setLocal * * Note that emitSignal is also called twice: once after * setLocal and once when the value from send is received * * @see send setLocal setValue value **/ void changeValue(type v) { send(v); setLocal(v); } /** * Saves the object to a stream. * @param stream The stream to save to **/ virtual void save(TQDataStream &stream) { stream << mData; } /** * @return The local value (see setLocal) if it is existing, * otherwise the network value which is always consistent on every * client. **/ const type& value() const { return mData; } /** * Reads from a stream and assigns the read value to this object. * * This function is called automatically when a new value is received * over network (i.e. it has been sent using send on this or any * other client) or when a game is loaded (and maybe on some other * events). * * Also calls emitSignal if isEmittingSignal is TRUE. * @param s The stream to read from **/ virtual void load(TQDataStream& s) { s >> mData; setDirty(false); if (isEmittingSignal()) { emitSignal(); } } /** * This calls setValue to change the value of the property. Note * that depending on the policy (see setAlwaysConsistent) the * returned value might be different from the assigned value!! * * So if you use setPolicy(PolicyClean): * \code * int a, b = 10; * myProperty = b; * a = myProperty.value(); * \endcode * Here a and b would differ! * The value is actually set as soon as it is received from the * KMessageServer which forwards it to ALL clients in the network. * * If you use a clean policy (see setPolicy) then * the returned value is the assigned value **/ const type& operator=(const type& t) { setValue(t); return value(); } /** * This copies the data of property to the KGameProperty object. * * Equivalent to setValue(property.value()); **/ const type& operator=(const KGameProperty& property) { setValue(property.value()); return value(); } /** * Yeah, you can do it! * \code * int a = myGamePropertyInt; * \endcode * If you don't see it: you don't have to use integerData.value() **/ operator type() const { return value(); } virtual const type_info* typeinfo() { return &typeid(type); } private: void init() { } private: type mData; }; typedef KGameProperty KGamePropertyInt; typedef KGameProperty KGamePropertyUInt; typedef KGameProperty KGamePropertyTQString; typedef KGameProperty KGamePropertyBool; #endif