00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087 virtual bool onInitialHandshake(FrameworkPacket * handshake, FrameworkPacket * reply) = 0;
00088
00089 virtual void onClientConnected(Client * client) = 0;
00090
00091 virtual void onClientDisconnected(Client * client) = 0;
00092
00093 virtual Client * createClient(ServerFramework *fw, const ost::InetHostAddress &addr, ost::tpport_t port) = 0;
00094 \endcode
00095 A thing to notice here is that you are forced to implement a method called createClient(...), this method should return an object of the class Client. But if you want to assosiate more data with a connected client, you can just inherit from the Client class and add what ever data you find usefull.
00096
00097 <hr>[Next: @ref Data ]
00098
00099
00100 @page Data Gameworld Data
00101 Most gameworlds are represented as a bunch of well organized data-structures. In strict server-client based games, the server holds a full copy of the world, and the client hold a subset of the data constituting the world. The datatypes might not even be the same on server and client, because the client might not need to know all about every entity in the game. This is why Ganef uses two distinct base classes for datatypes, ClientData and ServerData.
00102 <p>
00103 The idea is that every entity in your gameworld on the serverside inherit from ServerData. This gives you some very nice methods for replicating the data from server to one or more clients, the names and comments of these methods should explain what they do:
00104 \code
00105 public:
00106
00107 void sendCreateObjectToAll() const;
00108
00109 void sendCreateObject(const std::map<unsigned int, Client *> & clientlist) const;
00110 void sendCreateObject( Client * client) const;
00111
00112
00113 void sendUpdateToAll(char type, bool reliable) const;
00114
00115 void sendUpdate(const std::map<unsigned int, Client *> & clientlist, char type, bool reliable) const;
00116 void sendUpdate( Client * client, char type, bool reliable) const;
00117
00118
00119 void sendDestroyToAll() const;
00120
00121 void sendDestroy(const std::map<unsigned int, Client *> & clientlist) const;
00122 void sendDestroy( Client * client ) const;
00123 \endcode
00124 The important thing to notice here is that when you call the sendCreateXXX methods, a similar datastructure inheriting from ClientData will be initialized and inserted into the ClientFramework on the clients you specify. The important thing to notice here is that every class in your gameworld must have a unique id. This id should be returned by the serverside data with the virtual method:
00125 \code
00126
00127 virtual const unsigned int getClassId() const = 0;
00128 \endcode
00129 And on the client side you should register a static method as the "constructor" for the client side class. This could be done as follows:
00130 \code
00131 class MyServerData : public ServerData
00132 {
00133 private:
00134 double x,y;
00135
00136 public:
00137 virtual const unsigned int getClassId() const { return 42; };
00138
00139 virtual void fillCreateObjectPacket(FrameworkPacket * packet) const
00140 {
00141 packet->writeInt32((int)x);
00142 packet->writeInt32((int)y);
00143 }
00144
00145 };
00146
00147 class MyClientData : public ClientData
00148 {
00149 private:
00150 int x,y;
00151 public:
00152 MyClientData(ClientFramework * f, FrameworkPacket * p) : ClientData(f,p)
00153 {
00154 // Here you would read your class data from the packet
00155 x = p->readInt32();
00156 y = p->readInt32();
00157 }
00158 static ClientData * constructMyData(ClientFramework * f, FrameworkPacket * p)
00159 {
00160 return new MyClientData(f,p);
00161 };
00162 };
00163
00164 class GameClient : public ClientFramework
00165 {
00166 // [... class methods and members ...]
00167 } app;
00168 app.registerConstructor(42, &MyData::constructMyData);
00169 \endcode
00170 Now if the server has an object of class MyServerData and calls "sendCreateObject(client)", a FrameworkPacket will be created and inserted in the outqueue on the server. Once the packet is sent and received on the client the packet will hint the client about what constructor method to call (based on the class id, in this case "42") and thereby accuire a new object of class MyClientData. An important thing to notice is that MyClientData calls the constructor "ClientData(ClientFramework * f, FrameworkPacket * p)" of ClientData. This is <em>very important</em> because the ClientData should get to read from the packet first, because the very first thing in the packet is the <em>unique object id</em> see @ref UniqueFrameworkID.
00171 <p> This also gives you the option of deriving classes even more, and then in each contructor read what ever data is expected, hence reducing duplication of code. If you have the same inheritance structure on the server you must likewise call the method for writing the initial constructor packet recursively, here is an example:
00172 \code
00173 class MyServerBaseData : public ServerData
00174 {
00175 private:
00176 double x,y;
00177 public:
00178 virtual const unsigned int getClassId() const = 0; // Not implemented, because this class is still abstract
00179
00180 virtual void fillCreateObjectPacket(FrameworkPacket * packet) const
00181 {
00182 packet->writeInt32((int)x);
00183 packet->writeInt32((int)y);
00184 }
00185 };
00186 class MyServerRealData1 : public MyServerBaseData
00187 {
00188 private:
00189 std::string name;
00190 MyServerBaseData * best_friend;
00191 public:
00192 virtual const unsigned int getClassId() const { return 42; };
00193
00194 virtual void fillCreateObjectPacket(FrameworkPacket * packet) const
00195 {
00196 // Let our base class write first
00197 this->MyServerBaseData::fillCreateObjectPacket(packet);
00198 // Then we write
00199 packet->writeString(name);
00200 packet->writePtr(best_friend);
00201 }
00202 };
00203 \endcode
00204 On the client you would have:
00205 \code
00206 class MyClientBaseData : public ClientData
00207 {
00208 private:
00209 int x,y;
00210 public:
00211 MyClientBaseData(ClientFramework * f, FrameworkPacket * p) : ClientData(f,p)
00212 {
00213 // Here the base data is read (before us the ClientData class read its unique framework id)
00214 x = p->readInt32();
00215 y = p->readInt32();
00216 }
00217 };
00218
00219 class MyClientReal1Data : public MyClientBaseData
00220 {
00221 private:
00222 std::string name;
00223 MyClientBaseData * best_friend;
00224 public:
00225 MyClientBaseData(ClientFramework * f, FrameworkPacket * p) : MyClientBaseData(f,p)
00226 {
00227 // It's time to read our data
00228 name = p->readString();
00229 p->readPtr(this, &best_friend, f);
00230 }
00231
00232 static ClientData * constructMyData(ClientFramework * f, FrameworkPacket * p)
00233 {
00234 return new MyClientReal1Data(f,p);
00235 };
00236 };
00237 \endcode
00238
00239 <hr>[Next: @ref ServerSetup ]
00240
00241
00242 @page ServerSetup Setting up the server
00243 When you have created your derived classes of ClientFramework and ServerFramework you should tell the server application to start listening on some port, then enter its @ref MainLoop. Then to process any incomming packets from clients, then @ref MainLoop <em>must</em> call the keepAlive(...) method. When ever this method is called any packets from clients will be processed.
00244
00245 <hr>[Next: @ref ClientSetup ]
00246
00247
00248 @page ClientSetup Establish a Connection from a client
00249 When you want to connect to a game server you just call the initServerConnection(...) with a hostname and port number. Then the client will start trying to connect to the server. Like on the server side, you must enter the @ref MainLoop of your game, where you <em>must</em> call the keepAlive(...) method to make sure you process any incomming packets from the server.
00250
00251 <hr>[Next: @ref ConnectionEvents ]
00252
00253
00254 @page ConnectionEvents Connection Events
00255 - onConnect(...)
00256 - onDisconnect(...)
00257 - onClientConnect(...)
00258 To make sure that no two thread manipulate the same data at the same time, all these event-methods that you are forced to implement on server and client will not be called unless you call keepAlive(...). That is these methods will be called from the same thread of execution as your @ref MainLoop. This garanties that these methods can manipulate the gameworld data (on client or server). The threads started by the framework to handle sending, receiving and retransmitting packets will never touch the actual game-data, this <em>only</em> happens in the code you write, and this code will only be touched by one thread of execution.
00259
00260 <h2>Keeping the framework running</h2>
00261 As explained earlier, you must call the keepAlive(...) method in your @ref MainLoop. If you fail to do this very often, then the connection between server and client might drop, because the heartbeats sent out by the client are sent from there. This is not very convenient, and will likely be changed in the future.
00262
00263 <hr>[Next: @ref TestApplication ]
00264
00265
00266 @page TestApplication Complete Test Application
00267 To better show how this is all used, we have constructed a test-application. It uses SDL and TTF so these libraries must be installed to compile the testapplication.
00268 <p>
00269 The "game" is rather simple: the client connects to the server and is given a "snake" consisting of 10 bites. When the client moves the mouse he request that the head of his snake be moved to that possition. The server keeps telling all connected client where all snakebites are possitioned.
00270 <h3>%Client files</h3>
00271 <ul>
00272 <li>client_main.cpp</li>
00273 <li>testclientframework.cpp / testclientframework.h</li>
00274 <li>testclientsnakebite.cpp / testclientsnakebite.h</li>
00275 </ul>
00276
00277 <h3>Server Files</h3>
00278 <ul>
00279 <li>server_main.cpp</li>
00280 <li>testserverframework.cpp / testserverframework.h</li>
00281 <li>testserversnakebite.cpp / testserversnakebite.h</li>
00282 </ul>
00283
00284 <hr>[Next: @ref Plugins ]
00285
00286
00287 @page Plugins Creating Plugins
00288 [To be written]
00289
00290 <hr>[Next: @ref Installation ]
00291
00292
00293 @page Installation Installation
00294 [To be written]
00295
00296 <h2>Dependencies</h2>
00297 The framework uses <a href="http://www.gnu.org/software/commoncpp/">"Common C++"</a>. And the testapplication uses SDL, SDL_image, SDL_ttf.
00298
00299 <hr>[Next: @ref Misc ]
00300
00301
00302
00303
00304
00305 @page Misc Miscellaneous
00306 <h2>Wordlist</h2>
00307 - @anchor UniqueFrameworkID <b> Unique object id </b>: In the server framework of Ganef, every data entity (every object of a class derived from ServerData) will be given a unique id (unsigned integer 32 bit). This id will be used to uniquely identify the object, both on the server and on the client.
00308 - @anchor MainLoop <b> Main loop</b>: most games have some method containing a while-loop where all the game-related things are done. This would on the client often include: (process any user input) (get any data sent from the server) (draw the gameworld to the screen). On the server it could include: (get and process any actions sent from clients) (integrate the gameworld with time elapsed since last iteration) (send any updates to clients).
00309
00310
00311 */