Jump to content

  • Log In with Google      Sign In   
  • Create Account

RakNet : Please Explain


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
11 replies to this topic

#1 PunaProgrammer chris   Members   -  Reputation: 150

Like
0Likes
Like

Posted 10 June 2006 - 11:21 AM

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.

Sponsor:

#2 Anonymous Poster_Anonymous Poster_*   Guests   -  Reputation:

0Likes

Posted 10 June 2006 - 02:03 PM

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).



#3 PunaProgrammer chris   Members   -  Reputation: 150

Like
0Likes
Like

Posted 11 June 2006 - 10:47 PM

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.

#4 graveyard filla   Members   -  Reputation: 583

Like
0Likes
Like

Posted 12 June 2006 - 05:58 AM

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.

#5 JPulham   Members   -  Reputation: 120

Like
0Likes
Like

Posted 12 June 2006 - 10:43 PM

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

#6 PunaProgrammer chris   Members   -  Reputation: 150

Like
0Likes
Like

Posted 13 June 2006 - 06:48 PM

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.

#7 hplus0603   Moderators   -  Reputation: 5302

Like
0Likes
Like

Posted 13 June 2006 - 07:24 PM

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 tool
struct 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.h

STRUCT_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 header
template<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 struct
USHORT_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.


#8 PunaProgrammer chris   Members   -  Reputation: 150

Like
0Likes
Like

Posted 14 June 2006 - 11:23 AM

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.

#9 hplus0603   Moderators   -  Reputation: 5302

Like
0Likes
Like

Posted 14 June 2006 - 01:28 PM

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 declaration
template <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 struct
struct in;
//! Operations for outputting from a struct
struct 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.


#10 Anonymous Poster_Anonymous Poster_*   Guests   -  Reputation:

0Likes

Posted 14 June 2006 - 01:36 PM

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.



#11 Tecla   Members   -  Reputation: 122

Like
0Likes
Like

Posted 15 June 2006 - 04:07 AM

Quote:
Original post by Anonymous Poster
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.


Care to elaborate why you feel it isn't that good? RakNet in my experience is pretty good, and I've contributed to and/or used extensively quite a few open source projects (gtk/gtkmm/gtk#, mono, WinOSI, Y windows, to name a few).

Also, note that RakNet is only open source these days if you're not using it commercially, but that's peachy with me. For $100 when you release a commercial game using it (if that's all you can afford), it's great. And the documentation is definitely workable; I first learned about NAT punch-through via RakNet's documentation, for example. And I can now do NAT punch-through without RakNet in general because they described it well enough.

So, again, can you explain your beefs with some detail and backup?

#12 graveyard filla   Members   -  Reputation: 583

Like
0Likes
Like

Posted 19 June 2006 - 03:34 PM

Quote:
Original post by Anonymous Poster
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.


why did you leave out the most important part of that quote?

Quote:

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.


"I have ever used"

also, i was being conservative with that. i dare you to show me a free networking API with better documentation. Even OpenTNL, basically the equivalent with RakNet overall, which cost 350$? for a license, has poor documentation compared to RakNet (last I checked, its been awhile). please, show me any free networking API that has better docs then RakNet and i'd be surprised.

... annyway

Puna, I guess I misunderstood your question. It seemed to me like you want to send a struct with a header in front of it.

Quote:

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.


1) Yes, see number 2
2) Just make a "int packet_id" as the first member of your class. thats it. try to read beejs networking guide, it helps teach you the basics of how networking and memory work for things like this.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS