Game Engine Modules/Projects

Started by
30 comments, last by Hodgman 8 years, 3 months ago

Ah, but you could write a Game class in your engine, one that is basically an abstract base class:


struct Game {
  virtual void OnSetup() = 0;
  virtual void OnUpdate (float elapsed) = 0;
  virtual void OnRender (float elapsed) = 0;
  virtual void OnShutdown () = 0;
};

Then you expect the "user" of the library to create code that subclasses the Game object, implementing each function to perform the appropriate actions. Then you expect your game loop to be given a pointer to the user's game object when it runs:


struct MyAwesomeGame : Game {
  ... implement functions...
};

int main ()
  MyAwesomeGame game;
  GameLoop::Run(game);
}

And your "run" method takes a Game&, which allows dynamic dispatch (you could do a Game* if you wanted, but a reference works and enforces the idea that you have to pass one, you can't pass nullptr). At the appropriate points in your "run" method you call the appropriate methods of the game object, e.g., you call OnStartup before you enter the loop proper, OnShutdown right before you return, and OnUpdate and OnRender during the actual loop.

The above is a simplified form of the idea (you'd probably want to take a closer look at what goes on in what is currently your m_pEngine->Update() calls and the like, but it gets the basic point across. In "real world" implementation you might use a non-virtual interface idiom to ensure that things that are "supposed" to happen on Update or Render happens regardless of what the user puts in their derived implementation. But essentially what you're doing here is using the game class to provide the basic required functionality of the game engine (this may be what your 'engine' class currently does) and use inheritance to allow user code to implement methods that are called at the right point to supply custom, game-specific behavior.

Then you have only the game-specific code in the game project, and the general part of the code (the Game base class and/or the Engine class, the GameLoop class, etc) in the engine library.

This is the sort of thing I attempted when I first tried it, but I can't remember why it didn't work. I'll give it another go and see what results I get!

Thanks for the help Josh!

Advertisement


This is the sort of thing I attempted when I first tried it, but I can't remember why it didn't work

I remember now! it is because i have a component factory that uses static initialization, which if the statics are defined in the static library, don't get initialized :(

I remember now! it is because i have a component factory that uses static initialization, which if the statics are defined in the static library, don't get initialized sad.png

Lots of libraries use statics and wouldn't work at all if they didn't get initialized. Can you provide an example of what your code looks like when this doesn't appear to work as you expect?

I was given this code by another member of this forum a while ago. I still don't thoroughly understand it which may be what is impeding me in overcoming this.


namespace ComponentRegistry
{
	typedef Component* (*CreateComponentFunc)();
	typedef std::map<std::string, CreateComponentFunc> ComponentRegistry;

	inline ComponentRegistry& getComponentRegistry()
	{
		static ComponentRegistry reg;
		return reg;
	}

	template<class T>
	Component* createComponent()
	{
		return new T;
	}

	template<class T>
	struct RegistryEntry
	{
	public:
		static RegistryEntry<T>& Instance(const std::string& name)
		{
			// Because I use a singleton here, even though `COMPONENT_REGISTER`
			// is expanded in multiple translation units, the constructor
			// will only be executed once. Only this cheap `Instance` function
			// (which most likely gets inlined) is executed multiple times.

			static RegistryEntry<T> inst(name);
			return inst;
		}

	private:
		RegistryEntry(const std::string& name)
		{
			ComponentRegistry& reg = getComponentRegistry();
			CreateComponentFunc func = createComponent < T >;

			std::pair<ComponentRegistry::iterator, bool> ret =
				reg.insert(ComponentRegistry::value_type(name, func));

			if(ret.second == false)
			{
				// This means there already is a component registered to
				// this name. You should handle this error as you see fit.
			}
		}

		RegistryEntry(const RegistryEntry<T>&) = delete; // C++11 feature
		RegistryEntry& operator=(const RegistryEntry<T>&) = delete;
	};
}

And then for each component i have this macro in the .cpp file


#define COMPONENT_REGISTER(TYPE, NAME)  \
template<class T>                                                     \
        class ComponentRegistration;                                          \
                                                                              \
        template<>                                                            \
        class ComponentRegistration<TYPE>                                     \
        {                                                                     \
            static const ComponentRegistry::RegistryEntry<TYPE>& reg;       \
        };                                                                    \
                                                                              \
        const ComponentRegistry::RegistryEntry<TYPE>&                       \
            ComponentRegistration<TYPE>::reg =                                \
                ComponentRegistry::RegistryEntry<TYPE>::Instance(NAME);     \

When i run the version where the engine is static lib and the game and executable the component registry is empty

IMHO this kind of automatic registration via initialization of static globals, is an anti-pattern and should be avoided. The standard says that any of these statics that are never used, may also be never initialized, so the compiler is free to optimize them out of the program completely.
Rewrite it to not use globals and it will work (or to actually reference those globals from somewhere else so they're actually used by the program).

IMHO this kind of automatic registration via initialization of static globals, is an anti-pattern and should be avoided. The standard says that any of these statics that are never used, may also be never initialized, so the compiler is free to optimize them out of the program completely.
Rewrite it to not use globals and it will work (or to actually reference those globals from somewhere else so they're actually used by the program).

I've been reading quite a bit about it and everywhere i read, there is at least someone saying something along the lines of 'it is bad practice', but I can't think of any other "automatic" way of doing this. I want to be able to create new components in the game project and possibly in scripts in the future, so it needs to be automatic.

How would you recommend approaching this?

Instead of just throwing registration macros out into the global scope and praying they don't get optimized away, just ensure that they're included somewhere in the regular program flow, such as the main function.
e.g. instead of:
//blah.cpp
COMPONENT_REGISTER(Foo)
COMPONENT_REGISTER(Bar)
Write:
//myapp.cpp
int main()
{
  COMPONENT_REGISTER(Foo);
  COMPONENT_REGISTER(Bar);
}
Or:
//myapp.cpp
int main()
{
  MODULE_REGISTER(MyModule);
}

//mymodule.cpp
DEFINE_MODULE(MyModule)
{
  COMPONENT_REGISTER(Foo);
  COMPONENT_REGISTER(Bar);
}
This also gives the application full control over which parts of the engine get initialized, when, and in what order.

Yeah this is what i mean the only solution i can find on the interwebs is to reference or define the statics in the executable, but the game is the executable so that means every time a game is made I (or any other user for that matter) will need to manually register not only their components, but the engine components too.

Is this actually the ONLY way to do this? or might there be some elaborate way to conquer this?

Put registration inside a function in the engine somewhere and then yes, you have to add one line of code to every new game that uses the engine:


//myApp.cpp
int main()
{
  InitializeEngine();
}

The app's main references that function, which references the registration code, as in my 3rd ("Or:") example above.

Put registration inside a function in the engine somewhere and then yes, you have to add one line of code to every new game that uses the engine:


//myApp.cpp
int main()
{
  InitializeEngine();
}

The app's main references that function, which references the registration code, as in my 3rd ("Or:") example above.

Ahh okay, that way works a treat actually because i have to have Engine.Run(); so if that way would work that would be perfect. I will try it when i get home, Thanks!

This topic is closed to new replies.

Advertisement