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.
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:
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 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;
Declared in the following framework header file:
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() {}
};
Declared in the following framework header file:
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() {}
};
Declared in the following framework header file:
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:
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:
IMyExtensionAPtr pA = ...;
IMyExtensionBPtr pB = ...;
if (CryIsSameClassInstance(pA, pB))
{
...
}
This works on both shared and raw interface pointers.
Declared in the following framework header file:
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:
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.
|
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!
|
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.
|
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.
|
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.
|
CRYINTERFACE_SIMPLE(iname) | Convenience macro for the following sequence (probably the most common extension case):
|
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.
|
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.
|
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.
|
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.
|
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().
|
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().
|
CRYREGISTER_CLASS(implclassname) | Registers the extension class in the system. Required once per extension class at file scope.
|
MAKE_CRYGUID(high, low) | Constructs a CryGUID. Useful when searching the registry for extensions by class ID.
|
Concrete examples showing how all of these macros play together are given here.
Declared in the following framework header files:
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.
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.
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;
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.
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
{
...
};
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()...");
}
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());
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()
}
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
}
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).