-3

I have multiple macros at beginning of each GameObject child class header. This is intended, as the client might add any number of classes inheriting from GameObject, with any engine macro arguments.

However, this creates bloated and hard to read code:

SPAWNABLE(Dude); EDITORONLY(Dude); OPTIMIZE(Dude); RANDOMIZE(Dude); class Dude : public GameObject { // ... }; 

The easiest solution would be to have a macro that takes other macros as arguments:

ENGINE_CLASS(Dude, SPAWNABLE, EDITORONLY, OPTIMIZE, RANDOMIZE) : public GameObject { // ... }; 

How can a macro be placed inside of the ENGINE_CLASS macro?

Here is the GameObjectFactory, which has the SPAWNABLE macro, which allows the class to be spawned. It is an easy test for whether this "macro inside macro" implementation works:

// Allows GameObject to be spawnable through SpawnGameObject. No need to use this if the class is never spawned through the engine #define ENGINE_SPAWNABLE(CLASSNAME) \ class CLASSNAME; \ static bool CLASSNAME##Registered \ = (GameObjectFactory::GetInstance().GetGameObjectRegistry()[#CLASSNAME] = &GameObjectFactory::SpawnObject<CLASSNAME>, true) // Registers the class with the factory's registry, and connects that to a templated SpawnGameObject function // Spawns a class of selected name #define SpawnGameObject GameObjectFactory::GetInstance().SpawnGameObjectByName // Singleton game object factory class GameObjectFactory { public: // Gets instance of the simpleton static GameObjectFactory& GetInstance() { static GameObjectFactory Instance; return Instance; } // A templated function to spawn any registered GameObject template <typename TObject> static std::unique_ptr<Object> SpawnObject() { return std::make_unique<TObject>(); } // A factory function that spawns an object of the specified class name std::unique_ptr<Object> SpawnGameObjectByName(const std::string& Name); std::unordered_map<std::string, std::function<std::unique_ptr<Object>()>>& GetGameObjectRegistry(); // Returns the Registry of class names private: std::unordered_map<std::string, std::function<std::unique_ptr<Object>()>> Registry; // Registry that maps class names to factory functions }; 

The following compiles, however, results in Foo being unable to being spawned through the Factory, meaning this macro doesn't work.

#define ENGINE_CLASS(CLASSNAME, MACROS[]) \ #define MACROS MACRO[] #define ENGINE_CLASS(CLASSNAME, MACROS) \ class CLASSNAME 

...

ENGINE_CLASS(Foo, { ENGINE_SPAWNABLE }) : public GameObject { public: Foo() { printf("hey!"); } }; 
9
  • 7
    Any help would be appreciated. Stop using macros and learn about templates instead. It's hard to tell from the snippets you've given us exactly how you would do that, but it's an avenue worth exploring. Commented Apr 20, 2023 at 9:45
  • 1
    I do use templates, but I also need to create a registry macro so all GameObjects can be spawned in another file without #including them, as otherwise client would have to change engine code. Commented Apr 20, 2023 at 9:56
  • 1
    It looks like you need a variadic macro and a preprocessor-side foreach loop over the variadic list, is this correct? Commented Apr 20, 2023 at 10:07
  • The Boost library has some tools to work with lists in the preprocessor. It looks something like #define EVAL(r, data, elem) data(elem) and then BOOST_PP_SEQ_FOR_EACH(EVAL, CLASSNAME, CLASSES) and the class list looks like (SPAWNABLE)(EDITORONLY)(OPTIMIZE)(RANDOMIZE) - this would expand to SPAWNABLE(CLASSNAME) EDITORONLY(CLASSNAME) etc. It's a huge hack and the preprocessor simply isn't very good at this sort of thing. Commented Apr 20, 2023 at 10:22
  • You might consider whether you can do less with macros. What if you just make one registration list with all the classes, and then each class that is registered tells you whether it is spawnable? Commented Apr 20, 2023 at 10:23

1 Answer 1

0

If the topic is just replacing several macros by only one, like:

ALL_MACROS( Dude, SPAWNABLE, EDITORONLY, OPTIMIZE ) 

It can be done, but defining ALL_MACROS requires several substeps:

#define PARENS () // to stop prepro-interpretation #define ALL_MACROS_HLP_REM_PARENS() ALL_MACROS_HLP #define ALL_MACROS_HLP(prm, mac1, ...) \ mac1( prm ); \ __VA_OPT__( ALL_MACROS_HLP_REM_PARENS PARENS (prm, __VA_ARGS__) ) #define DEEP_SOLVE(...) DEEP_SOLVE1(DEEP_SOLVE1(DEEP_SOLVE1(DEEP_SOLVE1(__VA_ARGS__)))) #define DEEP_SOLVE1(...) DEEP_SOLVE2(DEEP_SOLVE2(DEEP_SOLVE2(DEEP_SOLVE2(__VA_ARGS__)))) #define DEEP_SOLVE2(...) DEEP_SOLVE3(DEEP_SOLVE3(DEEP_SOLVE3(DEEP_SOLVE3(__VA_ARGS__)))) #define DEEP_SOLVE3(...) IDEM(IDEM(IDEM(IDEM(IDEM(IDEM(IDEM(__VA_ARGS__))))))) #define IDEM(...) __VA_ARGS__ // to force prepro-interpretation #define ALL_MACROS(prm, mac1, ...) \ DEEP_SOLVE( ALL_MACROS_HLP(prm, mac1, __VA_ARGS__) ) 
Sign up to request clarification or add additional context in comments.

2 Comments

What is this code for and where is it from? It doesn't seem to do anything on it's own and does not compile as-is.
this code needs C++20 because it uses VA_OPT

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.