Skip to main content
GameDev.net gamedev.net
🔒 Locked

Adding higher-level features to C++ (evil macro advice please!)

Started by Hodgman Aug 27, 2008 at 8:11 PM 2 replies 1.5k views
Original Post
Hodgman
Hodgman
I'm trying to automate some code now, to reduce the amount of work required later. My motivation is some of the magic features of higher-level game programming languages, like Unreal-script, where you can use keywords like 'simulated' or replication statements like 'reliable if' -- this lets game programmers write network code at a conceptual level, without having to worry about nitty-gritty implementation details. Specifically, I'm looking for a way to add higher-level functioanlity to variables (like network replication), preferably with as little game-programmer effort as possible. I've come up with 3 approaches (code is psuedo-C++) and I'd appreciate any advice on how evil they are, or if they could be improved (e.g. using boosty techniques). #1 The good old interface Engine code
	class Interface
	{
	public:
		virtual void SetDefalts() = 0;
		virtual void Save( stream& s ) = 0;
		virtual void Load( stream& s ) = 0;
		virtual void WriteDiff( stream& s, const Interface& o ) = 0;
	};
Game code
	class Thing : public Interface
	{
		int m_one, m_two;
	public:
		virtual void SetDefalts()
		{
			m_one = 1;
			m_two = 2;
		}
		virtual void Save( stream& s )
		{
			s << m_one;
			s << m_two;
		}
		virtual void Load( stream& s )
		{
			s >> m_one;
			s >> m_two;
		}
		virtual void WriteDiff( stream& s, const Interface& o )
		{
			Thing* pO = dynamic_cast<Thing*>( &o );
			assert( pO );
			if( m_one != o.m_one ) {
				s.write_bit(1);
				s << m_one;
			} else
				s.write_bit(0);

			if( m_two != o.m_two ) {
				s.write_bit(1);
				s << m_two;
			} else
				s.write_bit(0);
		}
	}
#2 The all knowing base class Engine code
	class Base
	{
	public:
		void SetDefalts()
		{
			for each m_Vars as it
				it->SetDefalts( this->*data );
		}
		void Save( stream& s ) {
			for each m_Vars as it
				it->Save( this->*data, s );
		}
		void Load( stream& s ) {
			for each m_Vars as it
				it->Load( this->*data, s );
		}
		void WriteDiff( stream& s, const cname& o ) {
			for each m_Vars as it
				it->WriteDiff( this->*data, o.*data, s );
		}
	protected:
		struct VarInfo
		{
			pointer_to_member data;
			func_ptr SetDefault;
			func_ptr Save;
			func_ptr Load;
			func_ptr Diff;
		}
		std::vector< VarInfo > m_Vars;
	};
Game code
	class Thing : public Base
	{
		int m_one, m_two;
		SetDefaultFunctor<int> m_sdOne, m_sdTwo;
	public:
		Thing() : m_sdOne(1), m_sdTwo(2)
		{
			VarInfo infoOne = { &Thing::m_one, m_sdOne, &SaveInt, &DiffInt };
			VarInfo infoTwo = { &Thing::m_two, m_sdTwo, &SaveInt, &DiffInt };
			m_Vars.push_back( infoOne );
			m_Vars.push_back( infoTwo );
		}
	}
#3 Macro magic Engine code
#define MAKE_VAR( type, name, val )	\ 
	type name;			//
#define ASSIGN_VAR( type, name, val )	\ 
	name = val;			//
#define STORE_VAR( type, name, val )	\ 
	s << name;			//
#define LOAD_VAR( type, name, val )	\ 
	s >> name;			//
#define DIFF_VAR( type, name, val )	\ 
	if( name != o.name ) {		\ 
		s.write_bit(1);		\ 
		s << name;		\ 
	} else				\ 
		s.write_bit(0);		//

#define DoMagic( cname, list )				\ 
	private:					\ 
	list( MAKE_VAR )				\ 
	void SetDefalts() {				\ 
	list( ASSIGN_VAR )				\ 
	}						\ 
	public:						\ 
	void Save( stream& s ) {			\ 
	list( STORE_VAR )				\ 
	}						\ 
	void Load( stream& s ) {			\ 
	list( LOAD_VAR )				\ 
	}						\ 
	void WriteDiff( stream& s, const cname& o ) {	\ 
	list( DIFF_VAR )				\ 
	}						//
Game code
	class Thing
	{
		#define Variables( Var )		\ 
			Var( int, m_one, 1 )		\ 
			Var( int, m_two, 2 )		//

		DoMagic( Thing, Variables )

		#undef Variables
	};
#1 is the most familiar and easiest to implement, but it shifts all the work onto the game programmer, which is what I want to avoid. #2 cuts down the amount of work required of the game-programmer to just some kind of registration mechanism, but it seems overly complex. #3 uses all kinds of magic, but it's within the engine where magic belongs. The game programmer has a very easy time, but might be left with a bad taste in their mouth from having to use a #define like that... I'm really not sure which avenue to pursue here.
ZeroSum
ZeroSum
I won't be much help with the code... but have you ever looked at the code for Valve's Source SDK (Half Life 2)? They use a similar system for network variables IIRC and it might give you some ideas/code.
Antheus
Antheus
I'll just link this, which sums up the basic approach.

You have visitor function visit(Visitor, Type).

Then, you merely need to define one function in each class, something like:
template < class Visitor > void visit(Visitor & v) {  v.visit(var1, "Health", 100);  v.visit(var2, "Mana", 50);  v.visit(var3, "Score", 0);  v.visit(varn, "Name", "DefaultName");};


Defaults object would be implemented as:
struct Defaults {  template < class T >  void visit(T & value, const char *, const T & def_value) {    value = def_value;  }};
The name parameter will not even get compiled.

The diff would be implemented similarily, reader/writer is obvious.

This gives you trivial iteration abilities for most members. You may want to split read/write, can be done with template specialization.
Hodgman
Hodgman
Quote:
Original post by ZeroSum
have you ever looked at the code for Valve's Source SDK (Half Life 2)? They use a similar system for network variables IIRC and it might give you some ideas/code.
Yeah, I've got the Source SDK installed at home; I remember them having lots of magic macros as well, which would probably be educational. But now that I've seen Antheus' suggestion, I think I'll go with the macro-free approach.

Quote:
Original post by Antheus
you merely need to define one function in each class, something like:
template < class Visitor > void visit(Visitor & v) {  v.visit(var1, "Health", 100);  v.visit(var2, "Mana", 50);  v.visit(var3, "Score", 0);  v.visit(varn, "Name", "DefaultName");};

Thanks Antheus!

This does everything my macro did, but without all the evil!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.