Is there a better way to do this? (factory subclass enum switch pattern)
So I'm making a game and I have this annoying pattern I've been using for loading levels. I have a bunch of different "item" structures that all have the same style static create function, eg:
Exploder* Exploder::Create(const v3& position, const v3& scale, const quat& rot, int flags) {
auto itemId = static_cast<int>(Engine::getActiveWorld()->itemManager.items.size());
auto obj = new GameObject("snip...");
obj->getTypeId() = itemId;
Engine::getActiveWorld()->addObject(obj);
auto itm = new Exploder({ obj });
Engine::getActiveWorld()->itemManager.items.emplace_back(itm);
return itm;
}
That part is fine, but I don't enjoy maintaining the massive switch statement that actually calls these.
Item* ObjectManager::createItem(ItemType type, const v3& pos, int flags, const v3& scale, const quat& rot, const string& customName) {
Item* ptr;
switch (type) {
case ItemType::rope:
ptr = RopeItem::Create(pos, scale, rot, flags);
break;
case ItemType::torch:
ptr = Torch::Create(pos, scale, rot, flags);
break;
.... snip 50 more item types
My mind goes to Rust enums, and I wonder if there's a better construct in C++ to use than ... this.
Thanks in advance!
Edit: ok! Big thanks to u/AKostur for suggesting static array. This was the cleanest idea, it took some finessing to get the construction timing right but it worked so hell yeah!
What I did:
Changed Create to return an Item* instead of the actual type, since it's always upcasted anyways, now the signatures are all exactly identical.
then in enums.hpp I declare an array like sugested:
class Item;
using ItemCreateAction = Item*(*)(const v3&, const v3&, const quat&, int);
extern std::array<ItemCreateAction, static_cast<size_t>(ItemType::_count)> ItemCreateFuncs;
struct ItemArrayPlacer {
public:
constexpr ItemArrayPlacer(ItemType type, const ItemCreateAction& action) {
ItemCreateFuncs[static_cast<size_t>(type)] = action;
}
};
Previously I had tried a similar idea, but std::function seems not to be able to be constexpr, so changing to raw function pointers allows for constexpr-ing the constructor.
exploder.cpp after includes before any code:
const auto t = ItemArrayPlacer(ItemType::exploder, Exploder::Create);
now the object manager can just call the functions like so:
Item* ptr = nullptr;
auto typeIndex = static_cast<size_t>(type);
if (typeIndex < ItemCreateFuncs.size() && ItemCreateFuncs[typeIndex]) {
ptr = ItemCreateFuncs[typeIndex](pos, scale, rot, flags);
}
Sweet! Code is now organized much better! Thanks for the ideas everyone!