Core subsystem

From NeoWiki

The core subsystem is the heart of the engine. All other subsystems depend on the core for providing the basic services needed for platform management, basic containers and other base functionality. To begin and end using the engine, you use the Core singleton object.

For a more detailed description of the methods and classes, see the API documentation. All links to sections in the API documentation are external links in bold style.

The core subsystem is important, you will need to familiarize yourself with most of the classes and methods offered, most notably the Core class and the containers.

Table of contents

Core

The Core singleton class provides the base entry point for the engine with methods to initialize and shutdown the engine and its subsystems, platform and architecture information and a central configuration repository.

Initialization and shutdown

The calls for engine initialization and shutdown are initialize() shutdown(), and to access the core singleton object you use get().

The initialize method initializes the engine core, does runtime checks of system and CPU types, endianness and OS-specific startup calls. Once you have completed the call to this method, you can use the whole engine. You must call this method before using any other part of the engine!

The shutdown method shuts down the engine and all running subsystems, cleans up and releases and internal data and does any OS-specific function calls. After a call to this method, you must call initialize again before using the engine. Make sure you properly release any held engine resources before shutting down the engine! This includes any render, audio and input devices, and associated resources like scene objects, textures and sounds. If you have enabled the resource and reference counter debugger, you will get log messages about any resources still held, and their reference/allocation details.

Platform and runtime information

You can collect information about the runtime platform from the Core object. This includes information such as CPU architecture (getArchitecture), CPU ID string (getCPUID), CPU capabilities (getCPUCaps), system endianness (getByteorder) and platform (getPlatform).

If you prefer compile-time platform selection, use the NEO_PLATFORM_(WINDOWS|LINUX|MACOSX) preprocessor macros instead. For CPU architecture, use the NEO_ARCH_(X86|X86_64|PPC) preprocessor macros, and for byteorder the NEO_BYTEORDER_(LITTLE|BIG)ENDIAN macros.

The Core class also has a number of static methods for auxiliary information, such as current working directory in the native file system (getCurrentWorkingDirectory), the current engine version string (getVersionString) and the copyright string (getCopyrightString).

For simplified management of endian-specific data, the template method (swapByteorder) provides a generic implementation. You can inject a specialization of this method in your own code.

Configuration

The Core also holds a configuration repository of the engine type Config, which is a hashtable mapping strings to an object of any type. This core configuration repository is used for some internal engine flags, and is also usable by the application for storing application configuration data. The method to access the configuration repository object is getConfig, and the flags currently used internally by the engine are:

  • keepresources
    Type: bool
    If this flag is true, all loaded resources will keep all their internal data, even data which is not used at runtime. For example, a shader will keep all the targets which is discarded during compilation.

Containers

The core subsystem contains implementations of a number of standard template containers, trying to mimic the interface in the STL counterparts. The reason we decided to implement our own containers are purely for performance. Many STL implementations (especially the Microsoft one) are too slow, and in debug builds downright unusable in a high-performance environment such as a game engine.

Some of the containers need to compare elements (for example the hash table for matching keys, and the AVL tree for ordering elements). For this these containers use a comparator class given as a template type. The engine has a default comparator type implementation in Comparator. The methods used to sort and test keys/objects are equal, less and lessequal. See the API documentation for AVLTree for an example.

Array

The Array class matches std::vector, and is a plain dynamic array container. You can add, remove, copy and find elements in the array using the provided methods, and the iterator access allows the Array to be used with all STL algorithms. For a complete reference, see the API documentation.

HashTable

The hash table provides a generic sparsely populated associative array, where data objects of a generic type is associated with a key, also of a generic type. Access can be done randomly by key indexing or through sequential iterators (also compatible for use with STL algorithms). For a complete reference, see the API documentation.

Any

A container for objects of any type. Useful for passing arguments of generic types to non-template methods and for storing generic data of any type in objects. API documentation

List

A doubly-linked list template container. API documentation

Queue

A FIFO (first-in-first-out) queue/stack template container with push/pop functionality. API documentation

AVLTree

The AVL tree is a self-balancing binary tree. API documentation

Smart pointers

The engine provides a number of classes to wrap raw pointers for easier resource management and memory leak prevention.

RefCount

The RefCount is the base class for providing the reference count mechanism used in the Pointer template class. The two methods used are incRef to increase the reference count of the object and decRef to decrease the reference count and deallocate object if zero. You can use getRefCount to query the current reference count of an object.

A virtual destructor is used to enable decRef to delete itself when the reference count reaches zero. You should never have to access these methods directly. If you do find yourself in a situation where you need to call incRef/decRef, you are most probably doing something wrong and should look over your code again. If you still think you are doing things right, think again.

Pointer

The Pointer template is an intrusive smart pointer wrapper class, meaning it relies on the wrapped type to have the two incRef/decRef methods and internal reference counting mechanism, it is not managed outside the wrapped object. The class provides methods for implicit access to the wrapped object through the * and -> operators, boolean checks for null pointers through the ! operator and implicity type conversion operator to bool and also comparison operators ==, !=, <, <=, >, >= and assignment operator =.

Construction, destruction and assignment in the pointer does all management of the reference count of the wrapped object, and is safe for self-assignment.

You should never access an object wrapped in a smart pointer through a raw pointer! It will lead to undefined behaviour and hard-to-trace bugs. Always use smart pointers to access these objects.

The engine classes wrapped in smart pointers have typedef:d the template instantiantions for easier typing. A class named ClassName will typedef the pointer instance Pointer< ClassName > to ClassNamePtr. For example, a texture object is always accessed through a smart pointer of type neo::render::TexturePtr. The macro to declare the smartpointer typedef is NEO_DECLARE_SMARTPOINTER

ScopedPointer

The ScopedPointer template wraps a raw pointer in a single-instance scoped pointer for guaranteed resource deallocation when the scoped pointer runs out of scope. If you find yourself writing new/delete pairs inside a function, you could instead use a ScopedPointer to wrap the resource and not worry about premature function exits causing memory/resource leaks. If you want to wrap resources allocated with new[], use ScopedArray instead.

ScopedArray

The ScopedArray template works just like ScopedPointer but is written for new[]/delete[] array operations.

Exceptions

Exceptions are used in the engine to denote an unrecoverable error state in the method throwing the exception, and never to return a status message or value. Sometimes the application might be able to handle the exception and continue execution.

All exceptions used and thrown by the engine subsystems are derived from the base Exception class. A try {...} catch( const Exception& exception ) {} block will catch all engine exceptions, and you can use the what() method to get the error message, including a stack trace if available.

The most important derived exceptions are NotImplementedException, InvalidValueException, InvalidMethodException, UnreachableCodeException and SystemException. Also the file I/O might throw the exceptions FileNotFoundException, InvalidFileFormatException and InvalidPackageException, but usually those are handled internally in the engine and returning an error code in the higher level method (like for a request to load a texture).

Utility classes and methods

RadixSort

The RadixSort class is an implementation of a radix sorter for integers (signed and unsigned, 32 and 64 bit) and floats. It does not sort in-place, you access the sorted result as an array of indices into the given data set.

To use it, you pass the wanted data type to the constructor, then call sort to perform the sort and getIndices to get the sort results as an array of indices into the data array given to the sort method.

Config

The Config class provides a configuration repository mapping string keys to data of any type. It is basically a wrapper around a HashTable<std::string,Any> with some convenience and notification methods and scripting hooks.

To set a value for a key, you can use the methods set (template, any type), setString (string value), setFlag (boolean value) and unset (to remove a key).

To query the value of a key, use get (returns Any), getAs (template, returns data as requested type), getString (return data as string) and getFlag (return data as boolean). Only the first method is safe in a manner that it returns an Any object which is empty if the key has no associated value, the other methods expect you to "know" the data type.

In derived classes you can overload the onConfigChange notification method to get callbacks when keys change.

The config object is scriptable by binding a script method to the onConfigChange event. See the API documentation for Scriptable for more information.

Timer

The Timer provides a high-precision timer interface. The actual precision is platform-dependent, but all implementations utilize the best available platform-specific APIs. Methods to query elapsed time (getDeltaTime) and manual reset (reset) control are provided, for a complete reference see the API documentation.

Stack traces

The engine provides functions to get a stack trace, useful for debugging and error reporting. However, the stack trace is usually not useful or even available in release builds without debug symbols and stack frame pointers. To get a stack trace string, use getStackTrace. If you wish to jus grab the stack trace frame pointers for later processing into a full stack trace report string, use getStackTraceData together with getStackTraceFromData

Profiling

The engine provides a simple profiling system through the ProfileManager singleton class. It gives an hierarchial breakdown of where the execution time is spent on a per-block basis (not as detailed as external tools like Intel's VTune down to instruction level) and call counts. A block is started with a call to beginProfile with the block name, and a block is ended with a call to endProfile. If beginProfile is called multiple times with the same block name within the same parent context, the same single child block is used.

If the profiling system is disabled by not defining the NEO_ENABLE_PROFILING build config controller macro, the beginProfile and endProfile functions are inlined and empty.

To get the profiling report you use the getReport metod on the profiling manager.

Random generator

A pseudo-random number generator is provided in the Random class, implementing the Mersenne Twister algorithm.

Hash

The engine uses the hash template method to calculate the hash value of objects (for example for hashing the keys in a hash table). Specializations for pointers and strings (both std::string and C-style char* strings) are provided, and the default implementation works for all primitive types. If you wish to hash any other compound types you should inject a specialization of this method into the neo::core namespace for the wanted type.

String utility classes and methods

HashString

The HashString class provides a wrapper around a std::string with an associated hash value. Comparison methods are written to compare hash values for early-out when strings differ, and only compare actual string content when the hash values match (to eliminate false positives). Apart from the usual operators for comparison (!=,==), assignment (=,+=) and concatenation (+), the HashString provides an implicit type conversion to std::string as well as methods to get the hash value (getHash), the length of the string (length), the string data as std::string (str) and the string data as a C-style char* (c_str).

Utility methods

You can tokenize a string with explode, split in two parts along given separators with split, merge strings adding separators with merge, trim unwanted characters from the start and end of a string with strip and replace all occurrences of a substring with another string with replace. Some of the methods are also available for C-style char* string arguments.

Methods to convert a string to integer toInteger/toUnsignedInteger and float toFloat are provided for convenience., as well as various methods to convert an integer and float to a string with toString.

Localization and Unicode support

The engine provides support for unicode strings and basic localization management.

Unicode strings

The engine provides three classes for dealing with unicode strings in UTF-8, UTF-16 and UTF-32 formats. These classes provide basic string methods and you can convert freely between them. Other subsystems in the engine can use unicode strings, for example the font rendering in the GUI module.

Localization

The LocalizationManager provides the base functionality for localization management by storing named dictionaries containing localized unicode strings based on string keys. By setting the appropriate locale and using keys to get the corresponding localized strings you can easily provide translations of your games.

Traits classes

Noncopyable

Empty base class enforcing non-copyable traits in a class, meaning the copy contructor and assignment operators are disabled. All singleton objects are non-copyable (naturally), as well as many resource classes. To inherit the traits, simply public inherit this class (since it is empty as in no implemented methods, it does not bloat your class). API documentation

Singleton

The Singleton template class provides the base functionality for a singleton pattern. It enforces a single instance of the given class (noncopyable of course), construction at first use as well as lifetime management. Explicit deallocation is allowed, next use will construct a new singleton object. All singleton objects are registered with the engine core singleton manager and are deallocated internally when the engine shuts down.

To create a singleton class Foo, you inherit public from Singleton< Foo > and make the base class a friend class. Make the constructor protected or private, and optionally the destructor protected/private if you don't want to allow explicit deallocation. In the implementation file you declare the singleton with the macro NEO_IMPLEMENT_SINGLETON( Foo ) and call the base class in the constructor initializer list.

  • Header foo.h, declaration:
    class Foo : public neo::core::Singleton< Foo >, ...
    {
    ...
    friend class neo::core::Singleton< Foo >
    }
  • Source foo.cpp, implementation:
    NEO_IMPLEMENT_SINGLETON( Foo )
    Foo::Foo() : neo::core::Singleton< Foo >(), ...

To access the singleton instance of the class, you use the get method which returns a pointer to the singleton object of the correct type (the implemented class, not the base singleton class).

  • For class Foo inheriting from Singleton< Foo >, the call will look like Foo* p_foo = Foo::get()

Type information

You can use the type information metaclasses to get extended type information about a object or type. This can be used for runtime selection of code paths based on templated parameters (or data passed through an Any object). It is used in the scripting systems to bind the correct scripting hooks for an C++ object based on type to be able to handle both references and pointers to objects and lifetime management. There are also metaclasses for compile-time type information.

TypeInfo provides extended and easy-to-access type information through a bitfield of type flags denoting pointers or references, if it is a fundamental/arithmetic or a compound type, const and volatileness of the type and if it is a member pointer. It also provides the standard std::type_info for the type.

TypeTraits and TypeMoreTraits can be used to at compile-time prune the type to get base type, type suitable for argument passing, un-consted type, un-volatile type and more.