RakNet : Please Explain

Started by
10 comments, last by graveyard filla 17 years, 10 months ago
Okay, I'm trying to figure out how to use RakNet, but the documentation is not so good, IMO. Question 1: In all of the examples where I see bitstreams used, the variables are "encoded" seperatley. Could you just write an entire object to a bitstream rather than it's member varaibles? Question 2: If you just encode an object (by casting it to char*) and send it, how do you set the first byte to be the packet ID thing? I don't see this ANYWHERE in the documentation, only how to read the packet ID on the recieving end. Thank you for putting up with my confusedness.
Advertisement
Quote:Original post by PunaProgrammer chris
Okay, I'm trying to figure out how to use RakNet, but the documentation is not so good, IMO.

Question 1: In all of the examples where I see bitstreams used, the variables are "encoded" seperatley. Could you just write an entire object to a bitstream rather than it's member varaibles?

Question 2: If you just encode an object (by casting it to char*) and send it, how do you set the first byte to be the packet ID thing? I don't see this ANYWHERE in the documentation, only how to read the packet ID on the recieving end.

Thank you for putting up with my confusedness.


1)
Not all the object's data may need to be sent (ie- a NEXT pointer when an Object is in a link list) and often a client needs only a subset of the object data maintained by the server to be sent to it.

2)
You usually copy the data into the packet buffer. Thus you would have a header part of the buffer that would have the object type/index, etc... (telling the reciever what to do with the following data block -- how long it is, etc..) and then the data block itself (all or part of the Objects struct/record data).

Umm... sorry, but I really don't seem to be picking up on this very well.
Basically, what I'm trying to do is make a templated send packet function which takes an object and packet identifier, encodes it (either way) and sends it. Is this possible, and if so, what would it look like? I think I am missing some major concept here or something. Any help would be greatly appreciated.
Dont be so quick to label the documentation as "not so good". I would go as far as saying that RakNet has some of the best documentation of ANY open source library I have ever used - not even just networking API's. Not only do they clearly explain all functioanlity of RakNet, but they have step by step tutorials and many sample programs.

In fact, your questions have more to do with the basics of memory management and basic networking. Do yourself a favor and learn the basics of networking before trying to jump in and code a game (you can google for beejs network tutorial for a good start).

Furthermore, the 'not so good' RakNet documentation will take you step by step in sending a simple message which is what you are asking. It's one of the first things you will see in the docs, here.
FTA, my 2D futuristic action MMORPG
http://www.daveandrews.org/articles/irrlicht_raknet/

here is an example of using RakNet with irrlicht, you can pick out relevant info from there.

Also, a good way of learning is :
a) looking at the docs, they ARE useful and
b) searching through the header file for RakClient or RakPeer and BitStream
c) getting as many examples as posible and disecting them.

You could use string functions and classes to manipulate the data before converting it/sending it. (Irrlicht again, has some good classes for this type of thing.)
pushpork
Thanks for the link to the RakNet with irrlicht example, and for the suggestions.
So from what I understand, if I want to use bitstreams to send an object, then I will have to write the individual members of the object to the bitstream if I want them to arrive in a specific order on the recieving computer. Is this correct? This seems quite inconvient, but if I can't figure out any other way to do it, I guess I will have to do it like this.

And btw the step by step tutorial shows how to send a char*, which shows everything EXCEPT what I'm trying to figure out (how to get an object into a packet with the data in an order with the packet ID).

I guess my new question now is: Is it possible to get the order that member variables of a class are written in the class?
Or a I just coming at this from the completley wrong direction?

PS: I just noticed the latest documentation is for an older version of RakNet, so that might explain some of the problems I had with the docs.
You're looking for a general "marshalling" or "serialization" package. Unfortunately, C++ doesn't quite have enough metadata available to the compiler -- either runtime or compile time -- to do a good job of that. Any solution will end up using code generation, or macros, or templates.

For example, VERY BRIEFLY, here's what your code would look like in the three possible implementations:

// mystruct.gh -- feed to a code generation toolstruct SomeStruct {  int(0-1023) someValue;  float(0.01,10000) x, y, z;}// runs through a custom tool that you write, which spits out mystruct.h and mystruct.cpp:struct SomeStruct {  unsigned short someValue;  float x, y, z;};bool marshal(SomeStruct const * input, char ** outBytes, size_t * size) {  if (!put_bits_ushort(input->someValue, 12, outBytes, size)) return false;  if (!put_bits_float(input->x, 0.1, 10000, outBytes, size)) return false;  if (!put_bits_float(input->y, 0.1, 10000, outBytes, size)) return false;  if (!put_bits_float(input->z, 0.1, 10000, outBytes, size)) return false;  return true;}


// Macros; mystruct.hSTRUCT_BEGIN(MyStruct)STRUCT_INT_MEMBER(MyStruct, unsigned short, 12, someValue)STRUCT_FLOAT_MEMBER(MyStruct, 0.01, 10000, x)STRUCT_FLOAT_MEMBER(MyStruct, 0.01, 10000, y)STRUCT_FLOAT_MEMBER(MyStruct, 0.01, 10000, z)STRUCT_END(MyStruct)// Marshalling; mystruct.cpp// marshallingdefs.h arranges so that StructBegin() and STRUCT_INT_MEMBER() defines the appropriate code#include "marshallingdefs.h"#include "mystruct.h"// Usage; other files// usedefs.h arrange so that the struct is defined and the functions forward declared#include "usedefs.h"#include "mystruct.h"


// Some headertemplate<typename T> struct MarshalInfo;#define USHORT_BITS(bits,name)   struct name;   template<> struct MarshalInfo<name> {     typedef unsigned short Type;     enum { Bits = bits };   }struct MarshalTerminate {  template<typename typ> void get<typ>();};template<typename typ, typename base>struct MarshalBase : base {  template<> bool get<typ>(typename MarshalInfo<typ>::Type * out) {    *out = _myval; return true;  }  bool marshal(char ** out, size_t * data) {    return put_data(_myval, out, data) && base::marshal(out, data);  }  private: typename MarshalInfo<typ>::Type _myval;};// Declaring the structUSHORT_BITS(someValue,12);FLOAT_VALUES(x,0.01,10000);FLOAT_VALUES(y,0.01,10000);FLOAT_VALUES(z,0.01,10000);struct MyStruct : public MarshalBase<someValue,   MarshalBase<x, MarshalBase<y, MarshalBase<z, MarshalTerminate> > > >{};// To access members:MyStruct s;  unsigned short v = s.get<someValue>();  float x = s.get<x>();


This is one area where C# makes life easier. You can put arbitrary declarators on various members, and then inspect those at runtime using reflection, and use dynamic code generation to generate the marshalling functions. Quite nice.
enum Bool { True, False, FileNotFound };
Alright, thanks for the info about serialization. I decided to do a google on it, and found that the boost libraries contatain a serialization library. While they made code much neater than the examples you showed, it still didn't solve the problem of having to create a function for every class that I want to create packets from.
I may end up using the boost serialization library anyways, since it would lower the amount of code on the recieving end and I will be able to make a generalized send function.
Thanks for all the help.
Quote:it still didn't solve the problem of having to create a function for every class that I want to create packets from


You can have your class create that packet automatically, if you're using the last structure I hinted at. Here's some actual code, that actually works:

#pragma warning(push)#pragma warning(disable:4996)//! \internal base declarationtemplate <typename T> struct StructInfo;//! USHORT_BITS(name,bits) declares a structure field that will //! store unsigned integer values up to 'bits' bits in size, as //! an unsigned short within the struct. You reference the given //! 'name' in your structure declaration (Struct<name, ...>).//! \param name The name to give the field; must be a valid C //! identifier name.//! \param bits The number of bits to use for storage (only used //! by some marshalling visitors).#define USHORT_BITS(name,bits) &92;  struct name; &92;  template<> struct StructInfo<name> { &92;    typedef name Name; &92;    typedef unsigned short Type; &92;    static inline int getBits() { return bits; } &92;    static inline Type init() { return 0; } &92;  }//! FLOAT_RES_LIMIT(name,res,limit) declares a structure field that will //! store float values with a given resolution ('res') extending out to //! a positive and negative limit from the origin ('limit').//!  You reference the given //! 'name' in your structure declaration (Struct<name, ...>).//! \param name The name to give the field; must be a valid C //! identifier name.//! \param res The minimum step size to represent.//! \param limit The maximum magnitude to represent.//! \note Res and limit may be used by some marshalling visitors to //! encode the float as a fixed-resolution number.#define FLOAT_RES_LIMIT(name,rez,limit) &92;  struct name; &92;  template<> struct StructInfo<name> { &92;    typedef name Name; &92;    typedef float Type; &92;    static inline float getRez() { return rez; } &92;    static inline float getLimit() { return limit; } &92;    static inline Type init() { return 0; } &92;  }#pragma warning(push)#pragma warning(disable:4584) // already a base// Declare helpers in the system//! Operations for inputting into a structstruct in;//! Operations for outputting from a structstruct out;//! \internal An actual struct member (used to disambiguate access)template<typename N> struct StructMember {  typename StructInfo<N>::Type _memb;  StructMember() { _memb = StructInfo<N>::init(); }};//! You implement a partial specialization on StructOp::visit() //! to perform visitation (such as serializing or validating data).template<typename operation, typename type, typename container> struct StructOp {};//! You implement a partial specialization on StructInitialize::initialize() //! to start out visitation.template<typename operation, typename type, typename container> struct StructInitialize {};//! You implement a partial specialization on StructTerminate::terminate() //! to finalize visitation.template<typename operation, typename type, typename container> struct StructTerminate {};//! \internal Base is used to terminate the declaration of a user compound type.struct Base {  template<typename operation, typename topmost, typename data> bool visit_internal(data & o) {    return StructTerminate<operation, topmost, data>().terminate(o);  }};//! You use template struct to construct structures out of data types //! that you have defined with USHORT_BITS, FLOAT_RES_LIMIT and other //! such macros that define "struct member" names/types.//! The first parameter is a name/type; the second is another Struct //! (for what comes after the first member), or empty (default) to //! finish the struct.template<typename N, typename B = Base>struct Struct : StructMember<N>, B {  //! Call get<name>() to read the value of the member 'name'.  template<typename T> typename StructInfo<T>::Type const & get() const {    return StructMember<T>::_memb;  }  //! Call set<name>() to set the value of the member 'name'.  template<typename T> void set(typename StructInfo<T>::Type const & t) {    StructMember<T>::_memb = t;  }  //! Call visit() to visit all the members of the struct in order,   //! based on your StructInitialize, StructOp and StructTerminate   //! visitor class implementations.  template<typename operation, typename data> bool visit(data & o) {    return StructInitialize<operation, Struct, data>().initialize(o, *this)        && visit_internal<operation, Struct, data>(o);  }protected:  //! \internal get the visitation in the right order  template<typename operation, typename topmost, typename data> bool visit_internal(data & o) {    return StructOp<operation, topmost, data>().visit(o, StructMember<N>::_memb)        && B::visit_internal<operation, topmost, data>(o) ;  }};#pragma warning(pop)//! \internal marshal to strings (do not use resolution information)template<typename T>struct StructInitialize<out, T, std::string> {  bool initialize(std::string & out, T const & t) {    return true;  }};//! \internal marshal to strings (do not use resolution information)template<typename T>struct StructTerminate<out, T, std::string> {  bool terminate(std::string & out) {    out += "\n";    return true;  }};//! \internal marshal to strings (do not use resolution information)template<typename T>struct StructOp<out, T, std::string> {  //! \internal Put floats into the string  bool visit(std::string & out, float f) {    char buf[30];    sprintf(buf, ":%g", f);    out += buf;    return true;  }  //! \internal Put ushorts into the string  bool visit(std::string & out, unsigned short us) {    char buf[30];    sprintf(buf, ":%d", us);    out += buf;    return true;  }};#pragma warning(pop)


Note that this only serializes out to std::string, but swapping in a BitStream instead (and writing the "in" part) is pretty straightforward.

The draw-back is that you have to call "get<name>()" instead of using "name" even within the class (as you could make "struct X" be your class), but it builds all the serialization entirely automatically, and you don't have to write any class-specific functions.

Here's a small test routine that shows how it works:

USHORT_BITS(code, 10);FLOAT_RES_LIMIT(x, 0.001f, 5000);FLOAT_RES_LIMIT(y, 0.001f, 5000);FLOAT_RES_LIMIT(z, 0.001f, 5000);struct TestStruct1  : Struct<code,    Struct<x,    Struct<y,    Struct<z    > > > >{};void TestStruct(){  TestStruct1 ts, q;  ts.set<code>(10);  ts.set<x>(15.0f);  ts.set<y>(20.0f);  ts.set<z>(-100);  q = ts;  std::string v;  bool b = ts.visit<out>(v);  assert(b == true);  assert( v == ":10:15:20:-100\n" );  assert(ts.get<code>() == q.get<code>());  assert(ts.get<z>() == q.get<z>());}


Another alternative, that means that YOU won't have to write any code on your own, is to go the code generation route. Many packages (including original Sun RPC) go that route; it has benefits in ease for the programmer but also draw-backs in maintainability over time.
enum Bool { True, False, FileNotFound };
Quote:Original post by graveyard filla
Dont be so quick to label the documentation as "not so good". I would go as far as saying that RakNet has some of the best documentation ...

No it doesn't.
Quote:of ANY open source library...

No it,s not.

This topic is closed to new replies.

Advertisement