• Advertisement
Sign in to follow this  

problem with repeating code on client and server (MORPG)

This topic is 4754 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

hi, ive been working on a multiplayer online RPG for the past 9 months. anyway, ive noticed a pattern and im wondering if there is anything i can do about it. the client and server are 2 seperate programs and 2 seperate workspaces (im not releasing the server; it will run on a dedicated machine). anyway, i notice that i am cut and pasting a lot of code. for example class interfaces / implementation. they are very similar on the client and server, except the server doesnt render anything. so, for example, on the client i will have a Bullet class.. the server needs this too, BUT, it does not need some of the members (GLuint texture for example). so, instead of putting the Bullet.h file in the "common" directory that both the client and server share, i have to cut and paste them, and put a slightly modified version on the server. i dont like this that much though, and im worried they might get out of sync and its just going to keep piling up untill its too much to handle. is there anything i could do about this? pre proccessor macros or something? thanks for any help.

Share this post


Link to post
Share on other sites
Advertisement
Guest Anonymous Poster
Couldn't you make a Bullet class which couldn't render itself and then make a RenderableBullet or something inhert from it, or if the server also need some special functions you could have the bullet class which had the things that both of them wanted and then make a ClientBullet and a ServerBullet which both inhert from the bullet class.

Share this post


Link to post
Share on other sites
Quote:
Original post by graveyard filla
so, for example, on the client i will have a Bullet class.. the server needs this too, BUT, it does not need some of the members (GLuint texture for example).


So what? So it will have an unused texture field. Big freaking deal.

Share this post


Link to post
Share on other sites
It seems reasonable to make the client and server reuse code, if at all possible.

In fact, they could even be the same program.

If you have an object which is renderable, you could potentially either:

- Subclass a base Bullet into a RenderableBullet and a non-renderable one (possibly using the factory pattern)
- Make a Bullet (or indeed anything) using composition, and have some displayable component which is null, empty, or has a dummy value if you're in the server context
- Have the rendering code in the bullet anyway, but just not render anything on the server side (for example, have a method render which takes a View & object, which it then calls View::renderModel or something on). The server-side would have a dummy View object on which renderModel was a no-op

Mark

Share this post


Link to post
Share on other sites
It's not as pretty but another you can do is to #define out unneeded stuff. e.g.


class Bullet
{
// common stuff

#ifdef _SERVER_
bool ExtraServerValidation(...);
// etc
#endif

#ifdef _CLIENT_
void Render();
// etc
#endif
};


Try to do the stuff markr suggested first but if all else fails you can force the issue like above.

Share this post


Link to post
Share on other sites
Quote:
i notice that i am cut and pasting a lot of code


BADNESS!!!

We run the exact same code on client and server. The object, when created on the server, finds that it doesn't have a SoundPlayer or Renderer to play with, and thus won't render itself. The object, when created on the client, finds that it doesn't have an AuthoritativeStateRecorder, and thus won't checkpoint its state. (Loosely, that's what goes on -- in implementation, it's of course more involved)

There are ways of structuring this that is more decomposed and includes less useless code in the server binaries, if you really want to. However, you shouldn't write the same code twice -- that's a receipe for maintainance disaster.

Worst case, you can have a Bullet.cpp, and it uses #ifdefs for SERVER and CLIENT, and then you compile the same file with two different project settings, defining the appropriate symbol. It's not pretty, but it's better than nothing.

Share this post


Link to post
Share on other sites
@hplus

the method you are talking about, is it what markr's second suggestion is? e.g., give a pointer to a specific class as a member of the class. for example, make a RenderInterface* member for Character. on the server this would stay as null, but on the client i would instantiate it. then, i could actually use the exact same code on the client and server, by just checking if its null, and then rendering if its not. is this what your talking about, and is this what you mean by "exact" same code?

my only concern is that a lot of stuff is done in the client thats not in the server, and vice versa. more then just rendering (things like collision detection are slightly different on the server so the server can detect when a player switches maps). how do you account for stuff like that? would i have to make a seperate function that is called from outside of the "shared" functions or something?

i kind of like the idea the AP and mark suggested of having a general Bullet class, with ClientBullet and ServerBullet being in the different projects. to me this is cleaner to look at, and easier to make logic (e.g., when looking at the code, you dont have to think "is this for the server or the client"?). my only concern is this might be harder to maintain. im leaning towards this solution though.

thanks for anymore help.

Share this post


Link to post
Share on other sites
I've met this problem too. I've not find a best way to solve it. I tought to use #ifdef _CLIENT_ and #ifdef _SERVER_ as someone has just said. I think it is better than using inheritance because you'll avoid to see an explosion of classes (2 more classes for each common class (ie: BulletBase, BulletServer and BulletClient instead of Bullet only); the system may become hard to understand in a very short time.

On the other and, I don't like very much #ifdef... #endif code (they make my source no more indented and so a little harder to maintain/read).

However I try always to avoid cut&paste of source (and so I agree with hplus0603): I don't like to debug the same mistakes n times (n = # of cut&paste) ;)

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by graveyard filla
i kind of like the idea the AP and mark suggested of having a general Bullet class, with ClientBullet and ServerBullet being in the different projects. to me this is cleaner to look at, and easier to make logic (e.g., when looking at the code, you dont have to think "is this for the server or the client"?).


Yes. It is a modern concept known as "Object Oriented Programming". You should try looking it up, reading a few books - it's interesting stuff. Saves a lot of time and effort when writing code.

Sadly, lots of gits (including more than one university lecturer I've met) decide to teach imperative programming and call it OOP in order, presumably, to save their jobs. WHy the heck else woudl they bother to do this yet not actually teach OOP? Shrug.

Quote:
my only concern is this might be harder to maintain. im leaning towards this solution though.


Um, no. A million (approximately) times *easier* to maintain...

OK, I lied. Proably 5 times easier to maintain. There's things that will make it harder to maintain with OOP, but they're outweighed massively in simple cases like this and/or become vanishingly small, so overall it's definitely going to be a lot easier. Since you don't know OOP yet, I'd suggest not even worrying about it; when doing what you're doing now you have to get into some pretty complex / rare / niche program design to encounter the situations where OOP goes spectacularly wrong and starts costing more time than it saves.

redmilamber

Share this post


Link to post
Share on other sites
I would recommend against the ifdefs. I just mentioned them as a possibility.

Yes, the interfaces is one way of doing it, and I think it works fine for many things. Note that the NULL interfaces would go both ways -- if the object is supposed to detect when it's outside the map bounds, then the OutsideMapBoundsTester interface returns NULL on the client.

We have more execution environments than simply server and client because of ghosting, etc, so being able to mix-and-match is quite useful to us. For example, an avatar that is being piloted by the local player probably does some things differently than an avatar that's being piloted by someone on another machine. You don't want to be creating class Avatar, class ServerAvatar, class PilotedClientAvatar, class RemoteClientAvatar, class ServerShadowAvatar, ...

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by hplus0603
You don't want to be creating class Avatar, class ServerAvatar, class PilotedClientAvatar, class RemoteClientAvatar, class ServerShadowAvatar, ...


...which is the kind of scenario I was thinking of when mentioning OOP has potentially catastrophically bad uses. Although there are good ways of solving this whilst still being fully OOP in 90% of cases, there's a 10% which is a bitch to solve for. And within that, there's another ?25%? (encounter it too rarely to estimate frequency of occurrence) which is almost impossible unless you have a really really good OOP language (better than C++), and not alwasy even then.

For anyone interested, I strongly recommend AOP (aspect OP) as a complimentary technique to OOP that (was designed to) work in all the situations where OOP starts going messy and horrible. God only knows why Sun's executives are so thick that they've kept AOP out of Java when they had *such* a good opportunity to make a mainstream language with first-class AOP and OOP side-by-side / hand-in-hand. Sigh. The world is full of people with no vision and only miserable little ideas :(.

Share this post


Link to post
Share on other sites
Quote:
Original post by hplus0603
I would recommend against the ifdefs. I just mentioned them as a possibility.

Yes, the interfaces is one way of doing it, and I think it works fine for many things. Note that the NULL interfaces would go both ways -- if the object is supposed to detect when it's outside the map bounds, then the OutsideMapBoundsTester interface returns NULL on the client.

We have more execution environments than simply server and client because of ghosting, etc, so being able to mix-and-match is quite useful to us. For example, an avatar that is being piloted by the local player probably does some things differently than an avatar that's being piloted by someone on another machine. You don't want to be creating class Avatar, class ServerAvatar, class PilotedClientAvatar, class RemoteClientAvatar, class ServerShadowAvatar, ...


this seems like a very interesting design. so basically, you just make a class for each function (or possibly group of functions) that is specific to either the client or the server? then the class would have a bunch of pointers to these classes and the rest of the members are "shared" functions, e.g. the ones that will be the same on both programs? also, what about data that is specific to the client or server? do we stuff data into these "interfaces" or do we make the data a member of the class, but use a pointer and allocate like with we do with the classes? the whole "mix and match" thing is definetly a pretty sweet deal. anyway, do i have the right idea here? youre right about the whole Server/ClientClassName thing, it will eventually just start getting out of hand and thats a lot of code in different places you have to maintain.

btw redmilamber, i appreciate your help, but is it really necessary to be rude like that?

[Edited by - graveyard filla on January 6, 2005 12:08:07 AM]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by graveyard filla

btw redmilamber, i appreciate your help, but is it really necessary to be rude like that?


One man's rude is another man's tongue-in-cheek jibe.

It's 2005 and really everyone ought to know what OOP is, at least on a basic level. If you don't, you really *do* need to go and read about it, learn what it is and what it can do for you - the commentary was serious.

Share this post


Link to post
Share on other sites
im not sure what gave you the idea that my code was not written in OOP. i use encapsulation, polymorphism, inheritense, design patterns, etc. maybe your misunderstanding? i hope your not insinuating i dont know what the definition of OOP is. you shouldnt really talk about how a person writes code (especially rudely) untill youve actually seen their code.

[Edited by - graveyard filla on January 6, 2005 1:51:21 PM]

Share this post


Link to post
Share on other sites
"you don't want to be creating class Avatar, class ServerAvatar, class PilotedClientAvatar, class RemoteClientAvatar, class ServerShadowAvatar,..."

Another possible way around this is to do a policy based design as approached in the Modern C++ Design book. I am just looking into this style of programming and it could solve this particular problem:

You have a template Avatar class and it takes in as a template parameter a policy on who is controlling it.

Just something to consider when designing the engine.

Chris

Share this post


Link to post
Share on other sites
Quote:
Another possible way around this is to do a policy based design


That's exactly what I was describing, except I suggest using run-time (dynamic) policy binding. This actually matters, for example, there are run-time optimizations possible when you migrate the authoritative instance of some simulated object.

Share this post


Link to post
Share on other sites
hi,

im doing some reading and just trying to see if i have the right idea. how does this look?


class RenderInfo
{
public:

void Render();

private:

GLuint texture;
};

class CheckForMapChangeEvent
{
public:

void Check();
};

class ABunchOfSpecificClientStuff
{
public:

void some_func();
void another();
void one_more();
into foobarbaz();
};

class ABunchOfServerStuff
{
public:

void beep();
int burp();
};

class Character
{
public:
Character(bool is_for_client);

//the server and client share this!!
void Update();

//this one too!
void SomeFunctionTheyBothUse();

private:

RenderInfo *render_info;

CheckForMapChangeEvent *check_for_map_change_event;

ABunchOfSpecificClientStuff *client_stuff;

ABunchOfServerStuff *server_stuff;

int health;

//used by server only!
int *last_time_saved_to_database;
};

//ctor sets up for client / server
Character::Character(bool is_for_client)
{
if(is_for_client)
{
render_info = new RenderInfo(parameters);
client_stuff = new ABOSCS();
server_stuff = NULL;
check_for_map_change_event = NULL;
last_time_saved_to_databaser = NULL;
}
else
{
render_info = NULL;
client_stuff = NULL;
server_stuff = new ServerStuff();
check_for_map_change_event = new CheckForMapChangeEvent();
last_time_saved_to_database = new int(GetTime());
}
}

void Character::Update()
{
if(render_info)
render_info->Render();

if(client_stuff)
client_stuff->Beep();

if(server_stuff)
server_stuff->FooBarBaz();

if(check_for_map_change_event)
check_for_map_change_event->Check();

if(last_time_saved_to_database)
{
//save characer every 30 seconds
if(GetTime() - *last_time_saved_to_database > 30000)
Database.save(this);
}
}






so, am i WAY off here? i think i might be (please dont laugh). now, the inheritence route would look like this:


class Character
{
public:

virtual void Update() = 0;

ALL COMMON STUFF HERE
};

class ClientCharacter : public Character
{

public:

void Update();

ALL CLIENT SPECIFIC STUFF HERE

};

class ServerCharacter: public Character
{
public:

void Update();

ALL SERVER SPECIFIC STUFF HERE
};






to me it seems like they will actually have an equal amount of code. the problem with inheritence though is that the files will be in different places (e.g. Character is in ../Common, CharacterServer is in /Server, CharacterClient is in /Client), but this really isnt that big of a deal. the one main advantage of using composition though is that we can "mix and match" interfaces, which seems very nice. btw, if i am right in this design.. is it a good idea to keep everything in seperate classes, so that its easier to mix-n-match? or should i throw all server spcific stuff in one class, and client in another (e.g. have a "ABuncOfClientSpecificStuff / ABunchOfServerSpecificStuff" classes)? i think if i did this though, then it would totally defeat the purpose of the system and i should just use inheritence, no?

anyway, am i way off in this? a push in the right direction would help.. thanks!

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by graveyard filla
im not sure what gave you the idea that my code was not written in OOP.


OOP was primarily invented and adopted to solve the problem you describe in this thread. You cite a lot of fancy words, and they're all very good things, but that's not what OOP is about: it's about use and re-use of code in a multiple-person / long-lived project with minimal maintenance / re-write / bug-fix / bug-contamination effects. The concept of the object is the basis for avoiding most of the frequently recurring problems of large, long-lived project development and maintenance.

OOP != programming using objects (common misconception spread by lazy teachers), it's about understanding the core purpose and behaviour of the object (object == data + code that acts upon that data) and understanding the side-effects of this paradigm, of which there are many, both good and bad.

Shrug.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
(that's why it is "OOP" not "OP; it's oriented... ;))

Share this post


Link to post
Share on other sites
I have all game-related objects in a game.dll, which is a separate project in the same combine (like VS solutions). There is a folder for the entire combine, then subfolders for each project and a shared bin folder. All of the .dlls and .exes are put in there together. The client and server share the utility library and game object .dlls. The client also has its data files, engine, and additional libraries.

I agree that OOP is a lot more than putting everything in a class. I try to make my classes elegant and flexible, so I can reuse as much as possible. I'm still learning and I spend a lot of time rewriting stuff to make it fit together better, though.

Share this post


Link to post
Share on other sites
Your example is one way of implementing what I suggest.

The problem with object inheritance hierarchies is that you end up wanting diamond patterns.

For example, suppose you're using "CaracterDrivenByOtherNode" for player characters of other players on your machine.

Suppose now you want to support some kind of ghost object on your servers. A server ghost object shares a lot of code with a client-side remote-driven character, because it's remote-driven, too, but it runs in a server environment; it doesn't render, and it may have more powers over game state.

At that point, the inheritance case breaks down, unless you factor the behavior in a mix-in class, which for implementation reasons will end up being a template, which turns into aspect-oriented programming with static binding.


Regarding how to actually implement Character for server, client, and other situations, I would do something like:


Character::Character( ExecutionEnvironment * env )
: allMembers( 0 ) // use your imagination
{
if( env->renderer ) {
renderAspect = new CharacterRenderer( env );
}
if( env->authoritativeStateManager ) {
authoritativeState = new CharacterAuthoritativeState( env );
}
if( env->interpolationManager ) {
interpolator = new ForwardInterpolator( env );
}
else {
interpolator = new IdentityInterpolator( env );
}
}


The last case is important: it's often useful to treat the aspects as always available, but "doing nothing" -- it simplifies code. In fact, CharacterRenderer() could just stash "env", and when time comes to render, it would just return if env->renderer is NULL.

If you don't want zillions of classes, you could inline all of the behaviors in the character class, but short-circuit out if the appropriate environment isn't available. Thus, the Character could stash the "env" pointer, and in the Render() function, it would early-out if env->renderer is NULL. The extra overhead of the call and test is probably going to be small, if you design your aspects/interfaces at appropriate levels of abstraction.

Share this post


Link to post
Share on other sites
hi hplus,

thanks for the reply. i have a question though.. why are you passing the "ExecutionEnvironment pointer to all the constructos of the object?

also, im a little confused on whats going on in the constructor in general. at first i thought the stuff in the if statement were bool's, e.g. env->rederer or ( env->authoritativeStateManager. i thought you were first making one of these EE structs, filling in true / false for each interface you wanted to use, and sending the struct to the constructor to "assemble" your class.

in which case, i dont understand the last part of your post either [smile]. however i think i might get what your saying. (or if not, could i do this?) anyway, couldnt i just put all the stuff the client and server wanted specific in a single class? then, i would give the class an "execution environment" struct as well. when creating the object, i set which of the bool's are true/ false. then, i just check if one of the interfaces is true and if so i call a function (not a pointer members function, but one of the class itself). anyway, heres an example of what im talking about:

struct Character_Interfaces
{
bool can_render,
check_for_map_changes,
saved_to_database,
};

class Character
{
public:

Character(const Character_Interfaces &ci) : ci(ci) {}

//client specific
void Render();
//server specific
void CheckForMapChangeEvent();
//both use it
void Update();

private:

Character_Interfaces ci;

//server specific
int last_time_saved;

//client specific
GLuint texture;
};


void Character::Update()
{

if(ci.can_render)
Render();

if(ci.check_for_map_change)
CheckForMapChangeEvent();
}





see what im talking about? i think you were talking about something like this in your last paragraph, but im not sure.

thanks for any more help.

Share this post


Link to post
Share on other sites
My execution environment struct actually contains pointers to the specific subsystems. I e, env->renderer is a pointer to my object renderer interface. It also contains pointers to the file system interface, the event broadcaster interface, etc. That way, an object is quite self-contained; only the ExecutionEnvironment interface needs to be shared with the rest of the world.

The reason we split out the different aspects is that we have more than two different execution environments, so the mix/match works better with each aspect a separate configurable. For example, we have a mode where the same Character runs both in a server environment (authoritative) AND receives input AND renders; that's when running a debugging app locally (not networked) for inital development and testing.

Share this post


Link to post
Share on other sites
hi hplus,

im still a little confused about your whole EE system, but i just have to think about it more.

anyway, i have another question [smile]. i might be mistaken, but, with this system, doesnt this mean my server must now link to OpenGL and stuff?

btw, which of the 2 methods i posted do you think will work out better for me in the end? the second one seems a tad stranger then the first, but the first requires a bunch of classes..

thanks again.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement