Skip to end of metadata
Go to start of metadata

Overview

Over the years CryENGINE has grown into a complex piece of software which poses quite a challenge to newcomers and experienced users alike trying to understand, configure, run and possibly extend it. With the ever growing number of developers and licensees developing for and building on top of the engine, it becomes necessary to refactor it into extensions. This should allow simpler customization. Existing features can be unplugged (at least to some degree), replaced, customized as well as new features added. Moreover, it can prevent scattering the code for one feature across the engine's base modules in order to implement all required aspects. If executed well, such a refactoring makes it easier for the programmer to understand the system.

CryENGINE's extension framework is loosely based on some fundamental concepts found in Microsoft's Component Object Model (COM). The framework defines two base interfaces that each extension needs to implement, namely ICryUnknown and ICryFactory. These are similar to COM's IUnknown and IClassFactory. The interfaces serve as a base to instantiate extensions, allow interface type casting, as well as query and expose functionality.

The framework utilizes the concept of shared pointers and is implemented in a way to enforce their consistent usage which should help reducing the chance of resource leaks. Moreover, a set of C++ templates wrapped in a few macros is provided for convenience and to encourage engine refactoring into extensions. This glue code efficiently implements all base services and registers extensions within the engine. Additionally, a few helper functions implement type safe casting of interface pointers, querying the IDs of extension interfaces, and convenient instantiation of extension classes. Hence, writing tedious boilerplate code over and over again is unnecessary and the potential for introducing bugs is greatly reduced. An example is provided here. Should the provided glue code not be applicable, then the interfaces and base services need to be implemented manually.

Clients access extensions via a system wide factory registry. It allows searching for specific extension classes by either name or ID and iterating extensions using an given interface ID as key.

Recently the framework has been extended to allow extensions to expose certain internal objects they aggregate or are composed of. Those so called composites are extensions themselves; that is, they inherit from ICryUnknown. This allows reusing desired properties like type information at run time for safe casting and loose coupling.

GUIDs

To uniquely identify extensions and their interfaces, globally unique identifiers (GUIDs) are used. These are essentially 128-bit numbers generated by some algorithm to ensure they only ever exist once within a given system such as CryEngine. Without underlying language support such a guarantee is key to implement type safe casting of extension interfaces. Smaller numbers like 32-bit integers were deliberately not chosen. Chances are very high that some IDs would eventually clash with the result of some really nasty bugs that are hard to debug. Also, using GUIDs we do not impose a strict ID generation policy. People can just use tools like "Create GUID" in the Visual Studio IDE or the macro below and be sure this ID is not already taken or might be (re-)claimed in the future. This is really important for large scale development and licensees wishing to extend the engine on their own.

GUIDs are defined as follows...

struct CryGUID
{
	uint64 hipart;
	uint64 lopart;

	...
};

typedef CryGUID CryInterfaceID;
typedef CryGUID CryClassID;

Declared in the following framework header files:

  • CryCommon/CryExtension/CryGUID.h
  • CryCommon/CryExtension/CryTypeID.h

The following Visual Studio macro can be used to generate GUIDs conveniently within the IDE. Once added to the Macro Explorer it can be bound to a keyboard shortcut or (custom) toolbar. It'll write GUIDs to wherever the cursor is currently located in the source code editor window.

Public Module CryGUIDGenModule

    Sub GenerateCryGUID()
        Dim newGuid As System.Guid
        newGuid = System.Guid.NewGuid()

        Dim guidStr As String

        guidStr = newGuid.ToString("N")
        guidStr = guidStr.Insert(16, ", 0x")
        guidStr = guidStr.Insert(0, "0x")

        REM guidStr = guidStr + vbNewLine
        REM guidStr = guidStr + newGuid.ToString("D")

        DTE.ActiveDocument.Selection.Text = guidStr
    End Sub

End Module

ICryUnknown

ICryUnknown provides the base interface for all extensions. In cases where making it top of the class hierarchy is either not possible or desired (e.g. third party code), an additional level of indirection can be applied to still be able to expose such code via the extension framework. An example how to do this can be found here.

ICryUnknown is declared as follows...

struct ICryUnknown
{
	CRYINTERFACE_DECLARE(ICryUnknown, 0x1000000010001000, 0x1000100000000000)

	virtual ICryFactory* GetFactory() const = 0;

protected:
	virtual void* QueryInterface(const CryInterfaceID& iid) const = 0;
	virtual void* QueryComposite(const char* name) const = 0;
};

typedef boost::shared_ptr<ICryUnknown> ICryUnknownPtr;
  • GetFactory() returns the factory the given extension object was instantiated with. Using the provided glue code this function has constant run time.
  • QueryInterface() returns a void pointer to the requested interface if the extension implements it or NULL otherwise. This function was deliberately declared as protected to enforce usage of type safe interface casting semantics. Using the provided glue code this function has a (worst case) run time linear in the number of supported interfaces. Due to glue code implementation details no additional internal function calls are needed though. A generic code generator produces a series of instructions comparing interfaces IDs and returning a properly cast pointer.
  • QueryComposite() returns a void pointer to the queried composite if the extension exposes it or NULL otherwise. As with QueryInterface() this function was deliberately declared as protected to enforce type querying. The function has a (worst case) run time linear in the number of exposed composites.
  • Unlike in COM ICryUnknown misses AddRef() and Release(). Reference counting is implemented in an non-intrusive way via use of shared pointers returned by the framework when extension classes get instantiated.

Declared in the following framework header file:

  • CryCommon/CryExtension/ICryUnknown.h

ICryFactory

ICryFactory provides the base interface to instantiate extensions. It is declared as follows...

struct ICryFactory
{
	virtual const char* GetClassName() const = 0;
	virtual const CryClassID& GetClassID() const = 0;
	virtual bool ClassSupports(const CryInterfaceID& iid) const = 0;
	virtual void ClassSupports(const CryInterfaceID*& pIIDs, size_t& numIIDs) const = 0;
	virtual ICryUnknownPtr CreateClassInstance() const = 0;

protected:
	virtual ~ICryFactory() {}
};
  • GetClassName() returns the name of the extension class. Using the provided glue code this functions has constant run time.
  • GetClassID() returns the ID of the extension class. Using the provided glue code this functions has constant run time.
  • ClassSupports(iid) returns true if the interface with given ID is supported by this extension class. Otherwise returns false. Using the provided glue code this functions has a (worst case) run time linear in the number of supported interfaces.
  • ClassSupports(pIIDs, numIIDs) returns the pointer to an internal array of IDs enumerating all interfaces this extension class supports as well as the length of said array. Using the provided glue code this functions has constant run time.
  • CreateClassInstance() dynamically creates an instance of the extension class and returns a shared pointer to it. If the extension class is implemented as a singleton, it will return a (static) shared pointer wrapping the single instance of that extension class. Using the provided glue code this function has constant run time except for the cost of the constructor call for non-singleton extensions.
  • The destructor was declared protected to prevent explicit destruction from client side via delete, boost::shared_ptr<T>, etc. ICryFactory instances exist (as singletons) throughout the entire lifetime of any CryENGINE process and must not be destroyed.

Declared in the following framework header file:

  • CryCommon/CryExtension/ICryFactory.h

ICryFactoryRegistry

ICryFactoryRegistry is a system implemented interface providing clients with the means to query extensions. It is declared as follows...

struct ICryFactoryRegistry
{
	virtual ICryFactory* GetFactory(const char* cname) const = 0;
	virtual ICryFactory* GetFactory(const CryClassID& cid) const = 0;
	virtual void IterateFactories(const CryInterfaceID& iid, ICryFactory** pFactories, size_t& numFactories) const = 0;

protected:
	virtual ~ICryFactoryRegistry() {}
};
  • GetFactory(cname) returns the factory of extension class with given name. Otherwise returns NULL.
  • GetFactory(cid) returns the factory of extension class with given ID. Otherwise returns NULL.
  • IterateFactory() if pFactories != NULL it copies up to numFactories entries of pointers to extension factories supporting iid. numFactories returns the number of pointers copied. If pFactories == NULL numFactories returns the total amount of extension factories supporting iid.
  • The destructor was declared protected to prevent explicit destruction from client side via delete, boost::shared_ptr<T>, etc. ICryFactoryRegistry is a system interface and that exists throughout the entire lifetime of any CryEngine process and must not be destroyed.

Declared in the following framework header file:

  • CryCommon/CryExtension/ICryFactoryRegistry.h

Interface casting semantics

Interface casting semantics have been implemented to provide syntactically convenient and type safe casting of interfaces. The syntax was designed to conform with traditional C++ type casts and respects const rules.

ICryFactory* pFactory = ...;
assert(pFactory);

ICryUnknownPtr pUnk = pFactory->CreateClassInstance();

IMyExtensionPtr pMyExtension = cryinterface_cast<IMyExtension>(pUnk);

if (pMyExtension)
{
	// it's safe to work with pMyExtension
}

Interface casting works on raw interface pointers, too. Please consider the guidelines regarding raw interface pointers.

Declared in the following framework header file:

  • CryCommon/CryExtension/ICryUnknown.h

Querying interface identifiers

Occasionally, it is necessary to know the ID of an interface, e.g. to pass it to ICryFactoryRegistry::IterateFactories(). This can be done as follows...

CryInterfaceID iid = cryiidof<IMyExtension>();

Declared in the following framework header file:

  • CryCommon/CryExtension/ICryUnknown.h

Checking if pointers to different interfaces belong to the same class instance

IMyExtensionAPtr pA = ...;
IMyExtensionBPtr pB = ...;

if (CryIsSameClassInstance(pA, pB))
{
	...
}

This works on both shared and raw interface pointers.

Declared in the following framework header file:

  • CryCommon/CryExtension/ICryUnknown.h

Querying composites

Extensions can be queried for composites as follows:

IMyExtensionPtr pMyExtension = ...;

ICryUnknownPtr pCompUnk = crycomposite_query(pMyExtension, "foo");

IFooPtr pComposite = cryinterface_cast<IFoo>(pCompUnk);
if (pComposite)
{
	// it's safe to work with pComposite, a composite of pMyExtention exposed as "foo" implementing IFoo
}

Please note that although the result of crycomposite_query() might be NULL, it doesn't mean an extension doesn't expose a certain composite. It just might not have been created yet. The query can be rewritten as follows to gather more information:

IMyExtensionPtr pMyExtension = ...;

bool exposed = false;
ICryUnknownPtr pCompUnk = crycomposite_query(pMyExtension, "foo", &exposed);

if (exposed)
{
	if (pCompUnk)
	{
		// "foo" exposed and created

		IFooPtr pComposite = cryinterface_cast<IFoo>(pCompUnk);
		if (pComposite)
		{
			// it's safe to work with pComposite, a composite of pMyExtention exposed as "foo" implementing IFoo
		}
	}
	else
	{
		// "foo" exposed but not yet created
	}
}
else
{
	// "foo" not exposed by pMyExtension
}

As with interface casting composite, queries work on raw interface pointers, too. Please consider the guidelines regarding raw interface pointers.

Declared in the following framework header file:

  • CryCommon/CryExtension/ICryUnknown.h

Glue code

The following macros provide glue code to implement the base interfaces and services to support the framework in a thread safe manner. Extension implementers are strongly encouraged to use them.

Macro name and arguments

Description

CRYINTERFACE_DECLARE(iname, iidHigh, iidLow)

Used to declare an interface and associated ID. Protects the interfaces from accidentally being deleted on client side, i.e. allows destruction only via boost::shared_ptr<T>. Required once per interface declaration.

  • iname is the (C++) name of the interface as declared
  • iidHigh is the higher 64-bit part of the interface ID (GUID)
  • iidLow is the lower 64-bit part of the interface ID (GUID)

CRYINTERFACE_BEGIN()

Start marker of the interface list inside the extension class implementation. Required once per extension class declaration.

CRYINTERFACE_ADD(iname)

Marker to add interfaces inside the extension class declaration. It has to be declared in between CRYINTERFACE_BEGIN() and any of the CRYINTERFACE_END*() markers. Only declare interfaces the class directly inherits. If deriving from an existing extension class or classes their interfaces get added automatically. If an interface is declared multiple times, duplicates will be removed. It is not needed to add ICryUnknown. Beware that all other interfaces not declared herein won't be castable via cryinterface_cast<T>() later on!

  • iname is the (C++) name of the interface to be added

CRYINTERFACE_END()

End marker of the interface list inside the extension class declaration. Use this if not inheriting from any already existing extension class. Required once per extension class declaration. Mutually exclusive to any of the other CRYINTERFACE_END*() markers.

CRYINTERFACE_ENDWITHBASE(base)

End marker of the interface list inside the extension class declaration. Use this if inheriting from one already existing extension class. Required once per extension class declaration. Mutually exclusive to any of the other CRYINTERFACE_END*() markers.

  • base is the (C++) name of the extension class derived from

CRYINTERFACE_ENDWITHBASE2(base0, base1)

End marker of the interface list inside the extension class declaration. Use this if inheriting from two already existing extension classes. Required once per extension class declaration. Mutually exclusive to any of the other CRYINTERFACE_END*() markers.

  • base0 is the (C++) name of the 1st extension class derived from
  • base1 is the (C++) name of the 2nd extension class derived from

CRYINTERFACE_ENDWITHBASE3(base0, base1, base2)

End marker of the interface list inside the extension class declaration. Use this if inheriting from three already existing extension classes. Required once per extension class declaration. Mutually exclusive to any of the other CRYINTERFACE_END*() markers.

  • base0 is the (C++) name of the 1st extension class derived from
  • base1 is the (C++) name of the 2nd extension class derived from
  • base2 is the (C++) name of the 3rd extension class derived from

CRYINTERFACE_SIMPLE(iname)

Convenience macro for the following sequence (probably the most common extension case):

CRYINTERFACE_BEGIN()
	CRYINTERFACE_ADD(iname)
CRYINTERFACE_END()

CRYCOMPOSITE_BEGIN()

Start marker of the list of exposed composites.

CRYCOMPOSITE_ADD(member, membername)

Marker to add a member of the extension class to the list of exposed composites.

  • member is the (C++) name of the extension class member variable to be exposed. It has to be of type boost::shared_ptr<T> with T inheriting from ICryUnknown. This is enforced during compile time.
  • membername is name (as C-style string) of the composite under which it can later be queried at run time.

CRYCOMPOSITE_END(implclassname)

End marker of the list of exposed composites. Use this if not inheriting from any extension class that also exposes composites. Mutually exclusive to any of the other CRYCOMPOSITE_END*() markers.

CRYCOMPOSITE_ENDWITHBASE(implclassname, base)

End marker of the list of exposed composites. Use this if inheriting from one extension class that also exposes composites. Queries will first search in the current class and then look into the base class to find a composite matching the requested name specified in crycomposite_query(). Mutually exclusive to any of the other CRYCOMPOSITE_END*() markers.

  • implclassname is the (C++) name of the extension class to be implemented
  • base is the (C++) name of the extension class derived from

CRYCOMPOSITE_ENDWITHBASE2(implclassname, base0, base1)

End marker of the list of exposed composites. Use this if inheriting from two extension classes that also expose composites. Queries will first search in the current class and then look into the base classes to find a composite matching the requested name specified in crycomposite_query(). Mutually exclusive to any of the other CRYCOMPOSITE_END*() markers.

  • implclassname is the (C++) name of the extension class to be implemented
  • base0 is the (C++) name of the 1st extension class derived from
  • base1 is the (C++) name of the 2nd extension class derived from

CRYCOMPOSITE_ENDWITHBASE3(implclassname, base0, base1, base2)

End marker of the list of exposed composites. Use this if inheriting from three extension classes that also expose composites. Queries will first search in the current class and then look into the base classes to find a composite matching the requested name specified in crycomposite_query(). Mutually exclusive to any of the other CRYCOMPOSITE_END*() markers.

  • implclassname is the (C++) name of the extension class to be implemented
  • base0 is the (C++) name of the 1st extension class derived from
  • base1 is the (C++) name of the 2nd extension class derived from
  • base2 is the (C++) name of the 3rd extension class derived from

CRYGENERATE_CLASS(implclassname, cname, cidHigh, cidLow)

Generates code to support base interfaces and services for an extension class that can be instanciated an arbitrary number of times. Required once per extension class declaration. Mutually exclusive to CRYGENERATE_SINGLETONCLASS().

  • implclassname is the C++ class name of the extension
  • cname is the extension class name under which it is registered in the registry
  • cidHigh is the higher 64-bit part of the extension's class ID (GUID) under which it is registered in the registry
  • cidLow is the lower 64-bit part of the extension's class ID (GUID) under which it is registered in the registry

CRYGENERATE_SINGLETONCLASS(implclassname, cname, cidHigh, cidLow)

Generates code to support base interfaces and services for an extension class that can be instanciated only once (singleton). Required once per extension class declaration. Mutually exclusive to CRYGENERATE_CLASS().

  • implclassname is the C++ class name of the extension
  • cname is the extension class name under which it is registered in the registry
  • cidHigh is the higher 64-bit part of the extension's class ID (GUID) under which it is registered in the registry
  • cidLow is the lower 64-bit part of the extension's class ID (GUID) under which it is registered in the registry

CRYREGISTER_CLASS(implclassname)

Registers the extension class in the system. Required once per extension class at file scope.

  • implclassname is the C++ class name of the extension

MAKE_CRYGUID(high, low)

Constructs a CryGUID. Useful when searching the registry for extensions by class ID.

  • high is the higher 64-bit part of the GUID
  • low is the lower 64-bit part of the GUID

Concrete examples showing how all of these macros play together are given here.

Declared in the following framework header files:

  • CryCommon/CryExtension/Impl/ClassWeaver.h
  • CryCommon/CryExtension/CryGUID.h

Shared and raw interface pointers

The framework was designed and implemented to utilize shared pointers and enforce their usage in order to reduce the possibility of resource leaks. Raw interface pointers can still be acquired though. Because of that care needs to be taken to prevent re-wrapping those in shared pointer objects. This would break consistency of reference counting and eventually cause crashes unless the original shared pointer object gets passed during construction so its internal reference counter can be referred to. Best practice is to use raw interface pointers only to temporarily operate on interfaces and then just forget about them, i.e. don't store them for later use.

Using Extensions

Working with a specific extension class

To work with a specific extension class, clients need to know its class name or class id as well as the interface(s) supported. With this information its factory can be queried from the registry, an instance created and worked with as in the following example...

// IMyExtension.h
#include <CryExtension/ICryUnknown.h>

struct IMyExtension : public ICryUnknown
{
	...
};

typedef boost::shared_ptr<IMyExtension> IMyExtensionPtr;

// in client code
#include <IMyExtension.h>
#include <CryExtension/CryCreateClassInstance.h>

IMyExtensionPtr pMyExtension;

#if 0
// create extension by class name
if (CryCreateClassInstance("MyExtension", pMyExtension))
#else
// create extension by class id, guaranteed to create instance of same kind
if (CryCreateClassInstance(MAKE_CRYGUID(0x68c7f0e0c36446fe, 0x82a3bc01b54dc7bf), pMyExtension))
#endif
{
	// it's safe to work with pMyExtension
}
// verbose version of client code above
#include <IMyExtension.h>
#include <CryExtension/ICryFactory.h>
#include <CryExtension/ICryFactoryRegistry.h>

ICryFactoryRegistry* pReg = ...;

#if 0
// search extension by class name
ICryFactory* pFactory = pReg->GetFactory("MyExtension");
#else
// search extension by class id, guaranteed to yield same factory as in search by class name
ICryFactory* pFactory = pReg->GetFactory(MAKE_CRYGUID(0x68c7f0e0c36446fe, 0x82a3bc01b54dc7bf));
#endif

if (pFactory) // see comment below <1>
{
	ICryUnknownPtr pUnk = pFactory->CreateClassInstance();
	IMyExtensionPtr pMyExtension = cryinterface_cast<IMyExtension>(pUnk);
	if (pMyExtension)
	{
		// it's safe to work with pMyExtension
	}
}

As an optimization you can enhance the if check <1> as follows...

if (pFactory && pFactory->ClassSupports(cryiidof<IMyExtension>()))
{
	...

Changing the if statement as shown, will check interface support before instantiating the extension class, preventing potentially expensive construction and destruction of extensions incompatible to a given interface.

Finding extension classes supporting a specific interface

To determine how many extension classes in the registry support a given interface and have them listed, clients can submit queries similar to the following...

// IMyExtension.h
#include <CryExtension/ICryUnknown.h>

struct IMyExtension : public ICryUnknown
{
	...
};

// in client code
#include <IMyExtension.h>
#include <CryExtension/ICryFactory.h>
#include <CryExtension/ICryFactoryRegistry.h>

ICryFactoryRegistry* pReg = ...;

size_t numFactories = 0;
pReg->IterateFactories(cryiidof<IMyExtension>(), 0, numFactories);

ICryFactory** pFactories = new ICryFactory*[numFactories];

pReg->IterateFactories(cryiidof<IMyExtension>(), pFactories, numFactories);

...

delete [] pFactories;

Implementing extensions using the framework

The following section explains in detail how to implement extensions in CryEngine. It provides examples using and not using glue code and also shows how to utilize the framework in cases where ICryUnknown cannot be base of the extension interface.

Recommended layout to include framework header files

In the public interface header that will be included by the client...

// IMyExtension.h
#include <CryExtension/ICryUnknown.h>

struct IMyExtension : public ICryUnknown
{
	...
};

In the header file declaring the actual implementation class of the extension (if you're using glue code)...

// MyExtension.h
#include <IMyExtension.h>
#include <CryExtension/Impl/ClassWeaver.h>

class CMyExtension : public IMyExtension
{
	...
};

Using glue code

The first example shows a possible implementation of IMyExtension that was referred to in previous examples...

///////////////////////////////////////////
// public section

// IMyExtension.h
#include <CryExtension/ICryUnknown.h>

struct IMyExtension : public ICryUnknown
{
	CRYINTERFACE_DECLARE(IMyExtension, 0x4fb87a5f83f74323, 0xa7e42ca947c549d8)

	virtual void CallMe() = 0;
};

typedef boost::shared_ptr<IMyExtension> IMyExtensionPtr;

///////////////////////////////////////////
// private section not visible to client

// MyExtension.h
#include <IMyExtension.h>
#include <CryExtension/Impl/ClassWeaver.h>

class CMyExtension : public IMyExtension
{
	CRYINTERFACE_BEGIN()
		CRYINTERFACE_ADD(IMyExtension)
	CRYINTERFACE_END()

	CRYGENERATE_CLASS(CMyExtension, "MyExtension", 0x68c7f0e0c36446fe, 0x82a3bc01b54dc7bf)

public:
	virtual void CallMe();
};

// MyExtension.cpp
#include "MyExtension.h"

CRYREGISTER_CLASS(CMyExtension)

CMyExtension::CMyExtension()
{
}

CMyExtension::~CMyExtension()
{
}

void CMyExtension::CallMe()
{
	printf("Inside CMyExtension::CallMe()...");
}

The following example shows how extension class MyExtension can be customized and expanded to implement two more interfaces, IFoo and IBar...

///////////////////////////////////////////
// public section

// IFoo.h
#include <CryExtension/ICryUnknown.h>

struct IFoo : public ICryUnknown
{
	CRYINTERFACE_DECLARE(IFoo, 0x7f073239d1e6433f, 0xb59c1b6ff5f68d79)

	virtual void Foo() = 0;
};

// IBar.h
#include <CryExtension/ICryUnknown.h>

struct IBar : public ICryUnknown
{
	CRYINTERFACE_DECLARE(IBar, 0xa9361937f60d4054, 0xb716cb711970b5d1)

	virtual void Bar() = 0;
};

///////////////////////////////////////////
// private section not visible to client

// MyExtensionCustomized.h
#include "MyExtension.h"
#include <IFoo.h>
#include <IBar.h>
#include <CryExtension/Impl/ClassWeaver.h>

class CMyExtensionCustomized : public CMyExtension, public IFoo, public IBar
{
	CRYINTERFACE_BEGIN()
		CRYINTERFACE_ADD(IFoo)
		CRYINTERFACE_ADD(IBar)
	CRYINTERFACE_ENDWITHBASE(CMyExtension)

	CRYGENERATE_CLASS(CMyExtensionCustomized, "MyExtensionCustomized", 0x07bfa7c543a64f0c, 0x861e9fa3f7d7d264)

public:
	virtual void CallMe(); // chose to override MyExtension's impl
	virtual void Foo();
	virtual void Bar();
};

// MyExtensionCustomized.cpp
#include "MyExtensionCustomized.h"

CRYREGISTER_CLASS(CMyExtensionCustomized)

CMyExtensionCustomized::CMyExtensionCustomized()
{
}

CMyExtensionCustomized::~CMyExtensionCustomized()
{
}

void CMyExtensionCustomized::CallMe()
{
	printf("Inside CMyExtensionCustomized::CallMe()...");
}

void CMyExtensionCustomized::Foo()
{
	printf("Inside CMyExtensionCustomized::Foo()...");
}

void CMyExtensionCustomized::Bar()
{
	printf("Inside CMyExtensionCustomized::Bar()...");
}

Without using glue code

If for any reason using the glue code is neither desired nor applicable, extensions can be implemented as follows. It is recommended to implement ICryUnknown and ICryFactory such that their run time cost is equal to the one provided by glue code (as documented here and here).

///////////////////////////////////////////
// public section

// INoMacros.h
#include <CryExtension/ICryUnknown.h>

struct INoMacros : public ICryUnknown
{
	// befriend cryiidof and boost::checked_delete
	template <class T> friend const CryInterfaceID& InterfaceCastSemantics::cryiidof();
	template <class T> friend void boost::checked_delete(T* x);
protected:
	virtual ~INoMacros() {}

private:
	// It's very important that this static function is implemented for each interface!
	// Otherwise the consistency of cryinterface_cast<T>() is compromised because
	// cryiidof<T>() = cryiidof<baseof<T>>() {baseof<T> = ICryUnknown in most cases}
	static const CryInterfaceID& IID()
	{
		static const CryInterfaceID iid = {0xd0fda1427dee4cceull, 0x88ff91b6b7be2a1full};
		return iid;
	}

public:
	virtual void TellMeWhyIDontLikeMacros() = 0;
};

typedef boost::shared_ptr<INoMacros> INoMacrosPtr;

///////////////////////////////////////////
// private section not visible to client

// NoMacros.cpp
//
// This is just an exemplary implementation!
// For brevity the whole implementation is packed into this cpp file.

#include <INoMacros.h>
#include <CryExtension/ICryFactory.h>
#include <CryExtension/Impl/RegFactoryNode.h>

// implement factory first
class CNoMacrosFactory : public ICryFactory
{
	// ICryFactory
public:
	virtual const char* GetClassName() const
	{
		return "NoMacros";
	}
	virtual const CryClassID& GetClassID() const
	{
		static const CryClassID cid = {0xa4550317690145c1ull, 0xa7eb5d85403dfad4ull};
		return cid;
	}
	virtual bool ClassSupports(const CryInterfaceID& iid) const
	{
		return iid == cryiidof<ICryUnknown>() || iid == cryiidof<INoMacros>();
	}
	virtual void ClassSupports(const CryInterfaceID*& pIIDs, size_t& numIIDs) const
	{
		static const CryInterfaceID iids[2] = {cryiidof<ICryUnknown>(), cryiidof<INoMacros>()};
		pIIDs = iids;
		numIIDs = 2;
	}
	virtual ICryUnknownPtr CreateClassInstance() const;

public:
	static CNoMacrosFactory& Access()
	{
		return s_factory;
	}

private:
	CNoMacrosFactory() {}
	~CNoMacrosFactory() {}

private:
	static CNoMacrosFactory s_factory;
};

CNoMacrosFactory CNoMacrosFactory::s_factory;

// implement extension class
class CNoMacros : public INoMacros
{
	// ICryUnknown
public:
	virtual ICryFactory* GetFactory() const
	{
		return &CNoMacrosFactory::Access();
	};

	// befriend boost::checked_delete
	// only needed to be able to create initial shared_ptr<CNoMacros>
	// so we don't lose type info for debugging (i.e. inspecting shared_ptr)
	template <class T> friend void boost::checked_delete(T* x);

protected:
	virtual void* QueryInterface(const CryInterfaceID& iid) const
	{
		if (iid == cryiidof<ICryUnknown>())
			return (void*) (ICryUnknown*) this;
		else if (iid == cryiidof<INoMacros>())
			return (void*) (INoMacros*) this;
		else
			return 0;
	}

	virtual void* QueryComposite(const char* name) const
	{
		return 0;
	}

	// INoMacros
public:
	virtual void TellMeWhyIDontLikeMacros()
	{
		printf("Woohoo, no macros...\n");
	}

	CNoMacros() {}

protected:
	virtual ~CNoMacros() {}
};

// implement factory's CreateClassInstance method now that extension class is fully visible to compiler
ICryUnknownPtr CNoMacrosFactory::CreateClassInstance() const
{
	boost::shared_ptr<CNoMacros> p(new CDontLikeMacros);
	return ICryUnknownPtr(*static_cast<boost::shared_ptr<ICryUnknown>*>(static_cast<void*>(&p)));
}

// register extension
static SRegFactoryNode g_noMacrosFactory(&CNoMacrosFactory::Access());

Exposing composites

The following example shows how to expose (inherited) composites. For brevity the sample is not separated into files.

	//////////////////////////////////////////////////////////////////////////
	struct ITestExt1 : public ICryUnknown
	{
		CRYINTERFACE_DECLARE(ITestExt1, 0x9d9e0dcfa5764cb0, 0xa73701595f75bd32)

		virtual void Call1() = 0;
	};

	typedef boost::shared_ptr<ITestExt1> ITestExt1Ptr;

	class CTestExt1 : public ITestExt1
	{
		CRYINTERFACE_BEGIN()
			CRYINTERFACE_ADD(ITestExt1)
		CRYINTERFACE_END()

		CRYGENERATE_CLASS(CTestExt1, "TestExt1", 0x43b04e7cc1be45ca, 0x9df6ccb1c0dc1ad8)

	public:
		virtual void Call1();
	};

	CRYREGISTER_CLASS(CTestExt1)

	CTestExt1::CTestExt1()
	{
	}

	CTestExt1::~CTestExt1()
	{
	}

	void CTestExt1::Call1()
	{
	}

	//////////////////////////////////////////////////////////////////////////
	class CComposed : public ICryUnknown
	{
		CRYINTERFACE_BEGIN()
		CRYINTERFACE_END()

		CRYCOMPOSITE_BEGIN()
			CRYCOMPOSITE_ADD(m_pTestExt1, "Ext1")
		CRYCOMPOSITE_END(CComposed)

		CRYGENERATE_CLASS(CComposed, "Composed", 0x0439d74b8dcd4b7f, 0x9287dcdf7e26a3a5)

	private:
		ITestExt1Ptr m_pTestExt1;
	};

	CRYREGISTER_CLASS(CComposed)

	CComposed::CComposed()
	: m_pTestExt1()
	{
		CryCreateClassInstance("TestExt1", m_pTestExt1);
	}

	CComposed::~CComposed()
	{
	}

	//////////////////////////////////////////////////////////////////////////
	struct ITestExt2 : public ICryUnknown
	{
		CRYINTERFACE_DECLARE(ITestExt2, 0x8eb7a4b399874b9c, 0xb96bd6da7a8c72f9)

		virtual void Call2() = 0;
	};

	DECLARE_BOOST_POINTERS(ITestExt2);

	class CTestExt2 : public ITestExt2
	{
		CRYINTERFACE_BEGIN()
			CRYINTERFACE_ADD(ITestExt2)
		CRYINTERFACE_END()

		CRYGENERATE_CLASS(CTestExt2, "TestExt2", 0x25b3ebf8f1754b9a, 0xb5494e3da7cdd80f)

	public:
		virtual void Call2();
	};

	CRYREGISTER_CLASS(CTestExt2)

	CTestExt2::CTestExt2()
	{
	}

	CTestExt2::~CTestExt2()
	{
	}

	void CTestExt2::Call2()
	{
	}

	//////////////////////////////////////////////////////////////////////////
	class CMultiComposed : public CComposed
	{
		CRYCOMPOSITE_BEGIN()
			CRYCOMPOSITE_ADD(m_pTestExt2, "Ext2")
		CRYCOMPOSITE_ENDWITHBASE(CMultiComposed, CComposed)

		CRYGENERATE_CLASS(CMultiComposed, "MultiComposed", 0x0419d74b8dcd4b7e, 0x9287dcdf7e26a3a6)

	private:
		ITestExt2Ptr m_pTestExt2;
	};

	CRYREGISTER_CLASS(CMultiComposed)

	CMultiComposed::CMultiComposed()
	: m_pTestExt2()
	{
		CryCreateClassInstance("TestExt2", m_pTestExt2);
	}

	CMultiComposed::~CMultiComposed()
	{
	}

	...

	//////////////////////////////////////////////////////////////////////////
	// let's use it

	ICryUnknownPtr p;
	if (CryCreateClassInstance("MultiComposed", p))
	{
		ITestExt1Ptr p1 = cryinterface_cast<ITestExt1>(crycomposite_query(p, "Ext1"));
		if (p1)
			p1->Call1(); // calls CTestExt1::Call1()
		ITestExt2Ptr p2 = cryinterface_cast<ITestExt2>(crycomposite_query(p, "Ext2"));
		if (p2)
			p2->Call2(); // calls CTestExt2::Call2()
	}

If ICryUnknown cannot be base of the extension class

There are cases where making ICryUnknown the base of your extension class is not possible. Examples are legacy code bases that shouldn't be touched as well as 3rd party code for which you may not have full source code access or modifying it is sheer not practicable. Nonetheless, these code bases certainly provide a lot of functionality that would be very suited to be exposed as an engine extension (e.g. video playback, flash playback, etc). To encourage refactoring in such cases the following sample demonstrates how an additional level of indirection can help make it work.

///////////////////////////////////////////
// public section

// IExposeThirdPartyAPI.h
#include <CryExtension/ICryUnknown.h>
#include <IThirdPartyAPI.h>

struct IExposeThirdPartyAPI : public ICryUnknown
{
	CRYINTERFACE_DECLARE(IExposeThirdPartyAPI, 0x804250bbaacf4a5f, 0x90ef0327bb7a0a7f)

	virtual IThirdPartyAPI* Create() = 0;
};

typedef boost::shared_ptr<IExposeThirdPartyAPI> IExposeThirdPartyAPIPtr;

///////////////////////////////////////////
// private section not visible to client

// Expose3rdPartyAPI.h
#include <IExposeThirdPartyAPI.h>
#include <CryExtension/Impl/ClassWeaver.h>

class CExposeThirdPartyAPI : public IExposeThirdPartyAPI
{
	CRYINTERFACE_BEGIN()
		CRYINTERFACE_ADD(IExposeThirdPartyAPI)
	CRYINTERFACE_END()

	CRYGENERATE_CLASS(CExposeThirdPartyAPI, "ExposeThirdPartyAPI", 0xa93b970b2c434a21, 0x86acfe94d8dae547)

public:
	virtual IThirdPartyAPI* Create();
};

// ExposeThirdPartyAPI.cpp
#include "ExposeThirdPartyAPI.h"
#include "ThirdPartyAPI.h"

CRYREGISTER_CLASS(CExposeThirdPartyAPI)

CExposeThirdPartyAPI::CExposeThirdPartyAPI()
{
}

CExposeThirdPartyAPI::~CExposeThirdPartyAPI()
{
}

IThirdPartyAPI* CExposeThirdPartyAPI::Create()
{
	return new CThirdPartyAPI; // CThirdPartyAPI implements IThirdPartyAPI
}

Custom inclusion and exclusion of extensions

To allow easy configuration, CryEngine provides a global "extension definition" header much like CryCommon/ProjectDefines.h that is automatically included in all modules via platform.h. In it extension implementers state a #define that they wrap their extension implementation code with. This way users can easily comment out extensions they're not interested in, thus excluding unused extension code from their build. Interface headers must not be affected by those #ifdefs so client code compiles as is either way.

///////////////////////////////////////////
// public section

// IMyExtension.h
#include <CryExtension/ICryUnknown.h>

struct IMyExtension : public ICryUnknown
{
	...
};

typedef boost::shared_ptr<IMyExtension> IMyExtensionPtr;

// ExtensionDefines.h
...
#define INCLUDE_MYEXTENSION
...

///////////////////////////////////////////
// private section not visible to client

// MyExtension.h
#if defined(INCLUDE_MYEXTENSION)

#include <IMyExtension.h>
#include <CryExtension/Impl/ClassWeaver.h>

class CMyExtension : public IMyExtension
{
	...
};

#endif // #if defined(INCLUDE_MYEXTENSION)

// MyExtension.cpp
#if defined(INCLUDE_MYEXTENSION)

#include "MyExtension.h"

CRYREGISTER_CLASS(CMyExtension)

...

#endif // #if defined(INCLUDE_MYEXTENSION)

The possibility to remove extensions from the build requires clients to write their code in way not to take availability of an extension for granted (see here).

  • No labels