I have a scene graph which contains nodes. Each node has a transform and several components. I have a 2d scene editor and am currently doing the following to select a component to edit:
//Within the node class I can grab a list of components
template<typename ComponentType = Component>
std::vector<SafeComponent<ComponentType>> components(bool exactType = true) const {
std::lock_guard<std::recursive_mutex> guard(lock);
std::vector<SafeComponent<ComponentType>> matchingComponents;
if (exactType) {
for (auto&& currentComponent : childComponents) {
if (typeid(currentComponent.get()) == typeid(ComponentType)) {
matchingComponents.push_back(SafeComponent<ComponentType>(shared_from_this(), std::static_pointer_cast<ComponentType>(currentComponent)));
}
}
} else {
for (auto&& currentComponent : childComponents) {
auto castComponent = std::dynamic_pointer_cast<ComponentType>(currentComponent);
if (castComponent) {
matchingComponents.push_back(SafeComponent<ComponentType>(shared_from_this(), castComponent));
}
}
}
return matchingComponents;
}
Then in my editor code I have the following
auto componentList = controls->elementToEdit->components(false);
for (auto&& component : componentList) {
std::cout << typeid(*component.self().get()).name() << std::endl;
if (typeid(*component.self().get()) == typeid(MV::Scene::Sprite)) {
auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditSprite"), buttonSize, MV::toWide("S: " + component->id()));
button->onAccept.connect("click", [&,component](std::shared_ptr<MV::Scene::Clickable>) {
panel.loadPanel<SelectedRectangleEditorPanel>(std::make_shared<EditableRectangle>(component.cast<MV::Scene::Sprite>(), panel.content(), panel.resources().mouse));
});
}else if (typeid(*component.self().get()) == typeid(MV::Scene::Grid)) {
auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditGrid"), buttonSize, MV::toWide("G: " + component->id()));
button->onAccept.connect("click", [&, component](std::shared_ptr<MV::Scene::Clickable>) {
panel.loadPanel<SelectedGridEditorPanel>(std::make_shared<EditableGrid>(component.cast<MV::Scene::Grid>(), panel.content(), panel.resources().mouse));
});
}else if (typeid(*component.self().get()) == typeid(MV::Scene::Emitter)) {
auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditEmitter"), buttonSize, MV::toWide("E: " + component->id()));
button->onAccept.connect("click", [&, component](std::shared_ptr<MV::Scene::Clickable>) {
panel.loadPanel<SelectedEmitterEditorPanel>(std::make_shared<EditableEmitter>(component.cast<MV::Scene::Emitter>(), panel.content(), panel.resources().mouse));
});
}
}
I wanted to utilize the visitor pattern, but I don't want to seal my class to outsiders by creating a "Visitor" which has to be modified for every potential type of component. Thankfully I've found a cool conceptual work-around.
https://ciaranm.wordpress.com/2010/07/15/generic-lambda-visitors-or-writing-haskell-in-c0x-part-4/
If I could modify my Node::components method to return
std::vector<OneOf<[ParameterPackPseudoCode]>>
I could write something like this:
auto componentList = controls->elementToEdit->components<Sprite, Grid, Emitter>();
for (auto&& component : componentList) {
where(component,
[&](const SafeComponent<Sprite> &sprite){
auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditSprite"), buttonSize, MV::toWide("S: " + component->id()));
button->onAccept.connect("click", [&,component](std::shared_ptr<MV::Scene::Clickable>) {
panel.loadPanel<SelectedRectangleEditorPanel>(std::make_shared<EditableRectangle>(sprite, panel.content(), panel.resources().mouse));
});
},
[&](const SafeComponent<Grid> &grid){
auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditGrid"), buttonSize, MV::toWide("G: " + component->id()));
button->onAccept.connect("click", [&, component](std::shared_ptr<MV::Scene::Clickable>) {
panel.loadPanel<SelectedGridEditorPanel>(std::make_shared<EditableGrid>(grid, panel.content(), panel.resources().mouse));
});
},
[&](const SafeComponent<Emitter> &emitter){
auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditEmitter"), buttonSize, MV::toWide("E: " + component->id()));
button->onAccept.connect("click", [&, component](std::shared_ptr<MV::Scene::Clickable>) {
panel.loadPanel<SelectedEmitterEditorPanel>(std::make_shared<EditableEmitter>(emitter, panel.content(), panel.resources().mouse));
});
}
);
}
My question is, I want to write something like this:
myNode->components<Sprite, Grid, Emitter>();
And get back:
std::vector<OneOf<SafeComponent<Sprite>, SafeComponent<Grid>, SafeComponent<Emitter>>>
This has two issues I don't really know the syntax or methods to solve:
1) I need to be able to convert Sprite, Grid, Emitter in:
template<typename ...ComponentList> Node::components
Into
OneOf<SafeComponent<Sprite>, SafeComponent<Grid>, SafeComponent<Emitter>>
2) I need to be able to run typeid against each item in the parameter pack (...ComponentList) to ensure it actually matches the underlying types I am looking for when iterating over my component list inside Node in order to push_back only the desired elements into the matchingComponents vector.
If you look at how I am currently checking against a single type it's pretty simple, but what I really need is something like:
if(TypeMatchesAnyOf<...ComponentList>(currentComponent)){
matchingComponents.push_back(SafeComponent<ComponentType>(shared_from_this(), std::static_pointer_cast<TypeMatched<...ComponentList>(currentComponent)::type>(currentComponent)));
}
TypeMatchesAnyOf and TypeMatched::type are the two components to this that I could see being integral.