Hello,
I'm making good progress with my game engines dll plugin system. Basically, nothing but one constructor function needed to be exported by a plugin to make it work, up until now. While seperating my modules into seperate dll projects, I've come across one issue again I've ignored earlier. Now its really turning out to be a big problem. Take a look at a sample code from the shadow plugin:
auto vEntities = m_pEntities->EntitiesWithComponents<ShadowCaster>();
size_t count = 0;
for(auto pEntity : vEntities)
{
// do other stuff here ...
if(auto pLight = pEntity->GetComponent<Light>())
{
if(!pLight->pModel)
continue;
if(auto pMaterial = pLight->pModel->GetMaterial(2))
pMaterial->SetTexture(3, m_pTextures->Get(L"ShadowBlur" + conv::ToString(count)));
}
}
Now there is the line with pEntity-GetComponent<Light>(), which comes from the deferred light plugin. Up until now they've all be in the same project as my engine, so it wasn't a problem. Now that I'm completely seperating them, I've got to come up with a solution. Internally the shadow plugin will require the deferred light plugin to being loaded, so I've basically got three choices I can think of:
- Implicitely link to the light dll in the shadow dll project, export the Light-component class from the dll and use it directly there. My least favourite option, as is produces a quite unclean mess (exporting classes with possible storages of e.g. std::wstring, which I'm using excessively to exchange data in my components, etc...), and requires at least all header files of the first plugin to create another plugin that depends upon it. Not even talking about all the other issue like binary compatibility, etc...
- Have one header file like "Dev.h" for each plugin that possibly supports extentsion, where one/multiple pure-virtual classes are declared. The component and other classes being used will then derive both the engines component base class and this interface. The interface will have specific getter/setter methods for all the components attributes, as well as a method for retrieving the internal choosen id to identify the type of component, when querying a entity e.g. . This approach will still require one header file from the first plugin to create any extention to it. Plus, it will require some unsafe casting, since from the entity I can only return a reference/pointer to the internal component base class, which I'll then have to cast to a pointer to the plugins component interface. I hope that doesn't sound too confusing, thinking about it I'm not even 100% sure if this would work. Plus, it will introduce quite an amount of additional work for developers of a plugin to support extentions, and possibly generate a lot of plugins that aren't extendable because they lack such a "dev" header file.
- Last option, I'll put a special "dll-component" interface (pure virtual class) in the engine. It will have one generic getter and setter, which takes a string as identifier and a void* to some data, and another method for retrieving mentioned id. For standard components like developed inside the engine, this getters and setters are overridden with an emtpy implementation in the base component class. Only in the plugin the dev will need to implement it using e.g. a switch statement for the identifier string to map the hard-coded member attributes like name, size, etc... I'd then have each dll/plugin/module register their components id with a string, so that it can then later easily be accessed. The finished product would look like this:
auto vEntities = m_pEntities->EntitiesWithComponents<ShadowCaster>();
size_t count = 0;
for(auto pEntity : vEntities)
{
// do other stuff here ...
if(IComponentDll* pLight = pEntity->GetComponent(ComponentRegistry::GetId(L"Light")))
{
gfx::IModel* pModel = dynamic_cast<gfx::IModel*>(pLight->GetAttribute(L"Model")); // maybe some helper-function like GetComponentAttribute<gfx::IModel*>(pLight, L"Model");
if(!pModel)
continue;
if(auto pMaterial = pModel->GetMaterial(2))
pMaterial->SetTexture(3, m_pTextures->Get(L"ShadowBlur" + conv::ToString(count)));
}
}
Now this does still appear a little flawed to me. Mostly because it still puts some work to the developer (implementing out the setter/getter), and introduces eigther blind-trust unsafe static or dynamic casting (I don't really like dynamic casting that much). But at least I wouldn't have to export more code, and the interface part would be generic on the engines side.
_____________________________
So, what do you say? Which one of those methods, if any, would you use? If none of them appears good to you, what would you do different? All suggestions are welcomed!