Engine overview

From NeoWiki

This page gives a quick introduction of the engine subsystems the overall design of the engine. For deeper understanding of each subsystem and details of the implementations, use the appropriate pages linked below.

Table of contents

Overall engine design philosophy

The engine is designed be split into separate modules with a strict dependency hierarchy. Core modules gets used by higher-level modules, but not the other way around. The engine is also designed to have no third-party dependencies to make building and maintaining the engine easier. We hope you will appreciate the simplicitly this implies; no need to hunt down and build a bunch of third-party libraries before even getting started on building the engine and your application.

The engine is compiled into a single monolithic static library, also for simplicity reasons. What gets compiled into the library is controlled by a single build configuration header, /src/core/buildconfig.h. Before building the engine, make sure this header is configured to suit your needs. There is really no need to be extra careful with removing unused parts of the engine, since the linker will remove unused segments and symbols for you (that said, you would get a slightly faster compile time for the engine by removing features from the build config header).

The engine uses namespaces to separate modules. Everything is put inside the main engine namespace, neo, and each module is put inside a subnamespace of the same name as the module. For example, the core module classes are put in the neo::core namespace, and the OpenGL rendering backend is put in the neo::render::opengl namespace.

Static vs Dynamic linking

Some might wonder why the engine is built as a static library and not as a dynamic library. We decided to go this route after careful considerations of the pros and cons of both. Some of the classic pros of using shared dynamic libraries, and why it's not so in this case:

  • Sharing of dynamic library between applications. This is actually not a benefit at all considering that most application will not want to share the engine code with other applications, for the simple reason the engine is not a one-size-fits-all project. Most project will want to tweak the bits and bobs of the engine, disable some parts, enable others, and in the end the engine compiled for one project will not fit the needs of another.
  • Easier maintenance and smaller updates when updrading with patches. Since the engine will probably be on par with the application in compiled size, this benefit isn't that great in the end.
  • Faster compilation. Since the application does not have to be linked to the engine dynamic library, only the stub import library (or similar construct), linking times is usually faster for a dynamic linking scheme during compilation/linking in the development phase. This does not, of course, have any impact on run-time performance, so with a decent development system this point becomes irrelevant.

Som pros of using static linking:

  • Position-dependent code. Since the engine and application is statically linked, the compiler can generate position-dependent code which gives just slightly faster function calls in some cases.
  • Unused code stripping. When statically linking, the linker can strip away unused code and thus reducing total binary size compared to a separate executable and dynamic library, where all code has to be present in the dynamic library in case some application needs it.
  • Cross-module optimization. With static linking an optimizing compiler/linker can optimize code across object file boundaries between the game and engine, which is generally not possible without code duplication when using dynamic linking.

All in all, for a full-featured game engine static linking is usually a better choice.

Engine overview

The engine provides all the functionality needed by a game in a platform-independent API, including rendering, audio, video, input, file I/O, networking, animation, physics, GUI and scripting subsystems. It also provides a full tool chain with exporters for popular 3D modeling packages and integrated editors for scenes, materials, scripts and GUIs. With the provided source code, any customization for your project becomes a simple task.

The whole engine is built on top of the core subsystem (neo::core) which provides the basic services and utility classes the higher-level subsystems need.

Other base subsystems are the stream-based file I/O subsystem (neo::file), math core functionality (neo::math), logging facilities (neo::log), threading and task management subsystem (neo:::thread) and user input management (neo:::input).

On top of these the lower-level feature interfaces are built, such as networking (neo::network) with stream-based socket I/O and various network protocol implementations, image, sound and video I/O (neo::image, neo::sound and neo::video) with support for various file formats, and finally the two major modules for rendering (neo::render) and audio (neo::audio).

Serialization of objects and resource management is done through the low-level chunk I/O module (neo::chunkio) and the higher-level resource management subsystem (neo::resource).

The scripting subsystem (neo::script) provides the abstract base interface for prividing scripting integration in the engine, with implementations for Lua (neo::script::lua) and simple C++ callbacks (neo::script::command). Other languages or custom implementation can easily be added.

The modules for animation (neo::animation) and collision queries and detection (neo::collision) provide the final corner stones in the high-level scene management subsystem (neo::scene) which is responible for all space partitioning, scene rendering and spatial object management.

The GUI subsystem (neo::gui) provides a powerful subsystem for user interfaces that is easy to extend, both with C++ code and through scripting.

Core

The core module contains all classes for the engine core functionality and basic platform abstraction. It also contains base utility classes for containers, type information, strings and memory manipulation, and platform and architecture specific code (like dealing with endianess issues). For a more in-depth documentation of the core subsystem, read the Core subsystem page.

The core modules contains (among others) these important classes:

Core

This Core singleton class contains the base engine object used to manage all other engine objects. It also contains information about the runtime platform such as CPU type, capabilities and endianess. It is also the manager of all other engine singletons. Before you start using the engine in your code you should call the initialize method on the Core. When your application has finished using the engine (probably just before exiting) you should call the shutdown method to clean up the engine internal state and release any held resources. Make sure you properly release any resources you have allocated yourself before you call this method.

Containers

The core module provides highly efficient implementations of a number of standard containers. Array is a base template container modeled after the STL vector. We provide our own implementation since some popular implementations (like in the Microsoft C++ lib) are horribly slow in debug builds. HashTable provides storage for sparse arrays indexed by hashed keys of generic types. List provides a linked list, Queue a FIFO queue. AVLTree provides a balanced binary tree used in memory management.

Any is a container class for encapsulating a single object of any type. Modeled after boost::any. Used for example for associating generic data types to property keys, or for passing arguments through the scripting interface.

Utility classes

HashString is a std::string wrapper with automatic hashing for faster string compares. Activator provides a base interface for activated/deactivated state management.

The Config class provides a simple interface for a configuration repository where a generic data item can be associated with a string key. To store the data the Any class is used.

ProfileManager provides an interface for basic code profiling of code segments (not down to instruction level). Block execution counts and total execution time is recorded with hierarchy information of caller block to give percentage data. You can enable internal profiling in the engine in the build config header.

RefCount and Pointer provides the foundation for the reference counted objects in the engine. A reference counted object is always accessed through a smart pointer which is a specialization of the Pointer template type wrapping the raw object pointer in methods doing reference counting (the object is derived from the base RefCount class). You must always access reference counted objects through smart pointers. Failure to do so WILL lead to crashes and undefined behaviour!

ScopedPointer and ScopedArray provides a wrapper around raw pointers for safe allocation/deallocation management. The wrapped object is destroyed when the scoped pointer is destroyed, simplifying resource management for example in functions with multiple return paths.

Render

Render subsystem

Audio


Input


Network