Macros

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
nG Inverse
Posts: 115
Joined: April 27th, 2012, 11:49 pm

Macros

Post by nG Inverse » March 15th, 2018, 3:00 am

I recently finished Chili's UE4 tutorials and caught wind of his frustration with macros. I believe it was mentioned in prior videos of his as well. I too understand the headaches that come with trying to read atrocious macros.

The below example was made for the Pawn scripting language. It has extremely similar syntax as C, and AFAIK the pre-processor behavior is the same. The language does not support overloaded functions, so this was a simple work-around using macros.

Code: Select all

#define OF::%0(%1) static stock __%0__(){return _:SPF0:SPF1:0;}of_%0_(%1,)

// Check for parameters - if none, return immediately; otherwise, split for further parsing
#define SPF0:SPF1:0;}%0(,) 0;}%0()
#define SPF1:0;}%0(%1,%2) SPF2:SPF3:SPF4:SPF5:0;}%0(|||%1|||%2)

// Add checking for strings and ints - this assumes there are two+ parameters left
#define SPF2:SPF3:SPF4:SPF5:0;}%0(%9|||%1[]|||%2,%3) SPF2:SPF3:SPF4:SPF5:0;}%0s(%9%1[],|||%2|||%3)
#define SPF3:SPF4:SPF5:0;}%0(%9|||%1|||%2,%3) SPF2:SPF3:SPF4:SPF5:0;}%0i(%9%1,|||%2|||%3)

// Add checking for strings and ints - this assumes only one parameter is left
#define SPF4:SPF5:0;}%0(%9|||%1[]|||) 0;}%0s(%9%1[])
#define SPF5:0;}%0(%9|||%1|||) 0;}%0i(%9%1)
Note it checks for data types ints and strings. Really, the language only has 32-bit cells which represent all data. Essentially a string is just an array of cells, which is why when the preprocessor searches for braces ([]), it assumes a string.

The intended result of this was to have syntax like such

Code: Select all

// remember there are no "data types", everything is a "cell"
OF::Function(int_value){ /* ... */ }
OF::Function(string[]){ /* ... */ }
OF::Function(int_value,string[]){ /* ... */ }
Replace the function declaration with

Code: Select all

of_Function_i(int_value){}
of_Function_s(string[]){}
of_Function_is(int_value,string[]){}
A separate macro would be created to call it universally with Function(), and that other macro would parse the arguments, and call the appropriate function based on that. I'd be willing to go into more detail about the way the macro works if anyone is really interested. It's rather complicated until you get the hang of acting like a preprocessor.

Anyone else have some crazy macros to share?

nG Inverse
Posts: 115
Joined: April 27th, 2012, 11:49 pm

Re: Macros

Post by nG Inverse » March 15th, 2018, 3:28 am

Here are two examples of the macros being "expanded out" (recommend you paste these in visual studio or sublime text for legibility)

Code: Select all

// Supplied function
OF::GiveAchievement(player, const name[]){}

// Initial replacement from "OF::" macro
static stock __GiveAchievement__(){return _:SPF0:SPF1:0;}of_GiveAchievement_(player, const name[],)
// Replacement from "SPF1" macro
static stock __GiveAchievement__(){return _:SPF0:SPF2:SPF3:SPF4:SPF5:0;}of_GiveAchievement_(|||player|||const name[],)
// Replacement from "SPF3" macro
static stock __GiveAchievement__(){return _:SPF0:SPF2:SPF2:SPF3:SPF4:SPF5:0;}of_GiveAchievement_i(player,|||const name[]|||)
// Replacement from "SPF4" macro, which was the final result
static stock __GiveAchievement__(){return _:SPF0:SPF2:SPF2:SPF3:0;}of_GiveAchievement_is(player,const name[])

// --------------------------------------------------------------------------------------

// Supplied function
OF::GiveAchievement(player, ach_id, const name[]){}

// Initial replacement from "OF::" macro
static stock __GiveAchievement__(){return _:SPF0:SPF1:0;}of_GiveAchievement_(player, ach_id, const name[],)
// Replacement from "SPF1" macro
static stock __GiveAchievement__(){return _SPF0:SPF2:SPF3:SPF4:SPF5:0;}of_GiveAchievement_(|||player|||ach_id, const name[],)
// Replacement from "SPF3" macro
static stock __GiveAchievement__(){return _SPF0:SPF2:SPF2:SPF3:SPF4:SPF5:0;}of_GiveAchievement_i(player,|||ach_id|||const name[],)
// Replacement from "SPF3" macro
static stock __GiveAchievement__(){return _SPF0:SPF2:SPF2:SPF2:SPF3:SPF4:SPF5:0;}of_GiveAchievement_ii(player,ach_id,|||const name[]|||)
// Replacement from "SPF4" macro, which was the final result
static stock __GiveAchievement__(){return _SPF0:SPF2:SPF2:SPF2:SPF3:0;}of_GiveAchievement_iis(player,ach_id,const name[])
Note it generates another function (static stock __GiveAchievement__() ). This function will never actually be used/called. It is just a placeholder for the macros to be able to identify themselves by the preprocessor. Also the function returns something weird

Code: Select all

return _:SPF0:SPF2:SPF2:SPF2:SPF3:0;
The language has a "tag" system, similar to namespaces. They essentially are just there to make sure you pass the correct value to a function if one of the parameters is "tagged". If this is not followed, the compiler will either throw a warning or error. These tags can be nested like shown. The _: in from of them is essentially telling the compiler to drop the tag of the value/variable. So the function essentially just returns 0. It is also "static stock". Static in the language means it is "private" to the file it resides, and "stock" means the compiler won't throw warnings or errors if it is never used (in fact, will exclude it from the compiled source completely).

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Macros

Post by albinopapa » March 15th, 2018, 3:35 am

I think the way some library writers handle creating classes using the preprocessor as a shortcut is cunning, but a bit cheeky.

Code: Select all

class Entity
{
public:
     virtual void Update(float) = 0;
     virtual void Draw(Graphics&) = 0;
protected:
     Vec2 position,velocity;
};
#define MAKE_ENTITY_CHILD_CLASS( Name )
class #Name : public Entity\
{\
public:\
     void Update( float dt ) override;\
     void Draw( Graphics& gfx )const override;\
private:\
};

MAKE_ENTITY_CHILD_CLASS( Player );
MAKE_ENTITY_CHILD_CLASS( Enemy1 );
MAKE_ENTITY_CHILD_CLASS( Enemy2 );
MAKE_ENTITY_CHILD_CLASS( Enemy3 );
They create a template using the macro and just pass in the name and call the macros, the preprocessor will copy paste for you. I'm doing this from memory, so I may have somethings wrong, but I think I'm close.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Macros

Post by chili » March 15th, 2018, 3:43 am

There is a common operation in UE4 (forget which one it's been a while) where you have a normal C++ class object pointer, and you do myObj->SomeFunkyMacro( params ).

Never seen that one before. It's sneaky and clever and I absolutely hate it (partly because I spent like 10 minutes trying to figure out why the 'member function' in the example code wasn't showing up in my Intellisense candidate list. Oh yes of course, because that's not a member function, it's a macro silly.).
Chili

nG Inverse
Posts: 115
Joined: April 27th, 2012, 11:49 pm

Re: Macros

Post by nG Inverse » March 15th, 2018, 5:52 am

@albinopapa: I like your example! It seems simple enough. Unfortunately the API's I've explore seemed to be much more complex in their nature, and are much harder to trace the actual code the macros generate.

@chili: That's what frustrates me the most. And also, like you found on your exploration of UE4, most API's are poorly documented (even much worse than UE4, if at all). I know using existing and tested libraries is preferred (both from a code reproduction standpoint and as a time saver) but spending as much time to learn the interface versus writing it on my own seems counterproductive. At least if the functionality is made from scratch I know exactly what is going on at all times. Makes debugging complicated and sometimes hidden code much simpler.

Post Reply