Allow VariantArrayHandle CastAndCall to cast to concrete types

When you call VariantArrayHandle::CastAndCall, it now tries both basic
storage and virtual storage. You can modify the types of storages tried
by giving a type list of storage tags as the first argument.
This commit is contained in:
Kenneth Moreland 2019-01-16 22:31:55 -06:00
parent 5cf2a21d3c
commit d59ce11c00
6 changed files with 308 additions and 48 deletions

@ -0,0 +1,58 @@
# Allow VariantArrayHandle CastAndCall to cast to concrete types
Previously, the `VariantArrayHandle::CastAndCall` (and indirect calls through
`vtkm::cont::CastAndCall`) attempted to cast to only
`vtkm::cont::ArrayHandleVirtual` with different value types. That worked, but
it meant that whatever was called had to operate through virtual functions.
Under most circumstances, it is worthwhile to also check for some common
storage types that, when encountered, can be accessed much faster. This
change provides the casting to concrete storage types and now uses
`vtkm::cont::ArrayHandleVirtual` as a fallback when no concrete storage
type is found.
By default, `CastAndCall` checks all the storage types in
`VTKM_DEFAULT_STORAGE_LIST_TAG`, which typically contains only the basic
storage. The `ArrayHandleVirtual::CastAndCall` method also allows you to
override this behavior by specifying a different type list in the first
argument. If the first argument is a list type, `CastAndCall` assumes that
all the types in the list are storage tags. If you pass in
`vtkm::ListTagEmpty`, then `CastAndCall` will always cast to an
`ArrayHandleVirtual` (the previous behavior). Alternately, you can pass in
storage tags that might be likely under the current usage.
As an example, consider the following simple code.
``` cpp
vtkm::cont::VariantArrayHandle array;
// stuff happens
array.CastAndCall(myFunctor);
```
Previously, `myFunctor` would be called with
`vtkm::cont::ArrayHandleVirtual<T>` with different type `T`s. After this
change, `myFunctor` will be called with that and with
`vtkm::cont::ArrayHandle<T>` of the same type `T`s.
If you want to only call `myFunctor` with
`vtkm::cont::ArrayHandleVirtual<T>`, then replace the previous line with
``` cpp
array.CastAndCall(vtkm::ListTagEmpty(), myFunctor);
```
Let's say that additionally using `vtkm::cont::ArrayHandleIndex` was also
common. If you want to also specialize for that array, you can do so with
the following line.
``` cpp
array.CastAndCall(vtkm::ListTagBase<vtkm::cont::StorageBasic,
vtkm::cont::ArrayHandleIndex::StorageTag>,
myFunctor);
```
Note that `myFunctor` will be called with
`vtkm::cont::ArrayHandle<T,vtkm::cont::ArrayHandleIndex::StorageTag>`, not
`vtkm::cont::ArrayHandleIndex`.

@ -113,11 +113,10 @@ private:
template <typename T, class ArrayPortalType, class DeviceAdapterTag>
class ArrayTransfer<T, StorageTagImplicit<ArrayPortalType>, DeviceAdapterTag>
{
private:
public:
using StorageTag = StorageTagImplicit<ArrayPortalType>;
using StorageType = vtkm::cont::internal::Storage<T, StorageTag>;
public:
using ValueType = T;
using PortalControl = typename StorageType::PortalType;

@ -29,6 +29,7 @@
#include <vtkm/cont/ErrorBadType.h>
#include <vtkm/cont/Logging.h>
#include <vtkm/cont/StorageListTag.h>
#include <vtkm/cont/internal/DynamicTransform.h>
#include <vtkm/cont/internal/VariantArrayHandleContainer.h>
@ -173,13 +174,45 @@ public:
return VariantArrayHandleBase<NewTypeList>(*this);
}
/// Attempts to cast the held array to a specific value type,
//@{
/// \brief Call a functor using the underlying array type.
///
/// \c CastAndCall Attempts to cast the held array to a specific value type,
/// then call the given functor with the cast array. The types
/// tried in the cast are those in the lists defined by the TypeList.
/// By default \c VariantArrayHandle set this to VTKM_DEFAULT_TYPE_LIST_TAG.
/// By default \c VariantArrayHandle set this to \c VTKM_DEFAULT_TYPE_LIST_TAG.
///
template <typename Functor, typename... Args>
VTKM_CONT void CastAndCall(Functor&& f, Args&&...) const;
/// In addition to the value type, an \c ArrayHandle also requires a storage tag.
/// By default, \c CastAndCall attempts to cast the array using the storage tags
/// listed in \c VTKM_DEFAULT_STORAGE_LIST_TAG. You can optionally give a custom
/// list of storage tags as the second argument. If the storage of the underlying
/// array does not match any of the storage tags given, then the array will
/// be cast to an \c ArrayHandleVirtual, which can hold any array given the
/// appropriate value type. To always use \c ArrayHandleVirtual, pass
/// \c vtkm::ListTagEmpty as thefirst argument.
///
/// As previous stated, if a storage tag list is provided, it is given in the
/// first argument. The functor to call with the cast array is given as the next
/// argument (or the first argument if a storage tag list is not provided).
/// The remaning arguments, if any, are passed to the functor.
///
/// The functor will be called with the cast array as its first argument. Any
/// remaining arguments are passed from the arguments to \c CastAndCall.
///
template <typename FunctorOrStorageList, typename... Args>
VTKM_CONT void CastAndCall(FunctorOrStorageList&& functorOrStorageList, Args&&... args) const
{
this->CastAndCallImpl(vtkm::internal::ListTagCheck<FunctorOrStorageList>(),
std::forward<FunctorOrStorageList>(functorOrStorageList),
std::forward<Args>(args)...);
}
template <typename Functor>
VTKM_CONT void CastAndCall(Functor&& f) const
{
this->CastAndCallImpl(std::false_type(), std::forward<Functor>(f));
}
//@}
/// \brief Create a new array of the same type as this array.
///
@ -228,6 +261,18 @@ public:
private:
friend struct internal::variant::GetContainer;
std::shared_ptr<vtkm::cont::internal::VariantArrayHandleContainerBase> ArrayContainer;
template <typename Functor, typename... Args>
VTKM_CONT void CastAndCallImpl(std::false_type, Functor&& f, Args&&... args) const
{
this->CastAndCallImpl(std::true_type(),
VTKM_DEFAULT_STORAGE_LIST_TAG(),
std::forward<Functor>(f),
std::forward<Args>(args)...);
}
template <typename StorageTagList, typename Functor, typename... Args>
VTKM_CONT void CastAndCallImpl(std::true_type, StorageTagList, Functor&& f, Args&&...) const;
};
using VariantArrayHandle = vtkm::cont::VariantArrayHandleBase<VTKM_DEFAULT_TYPE_LIST_TAG>;
@ -254,6 +299,37 @@ namespace detail
{
struct VariantArrayHandleTry
{
template <typename T, typename Storage, typename Functor, typename... Args>
void operator()(brigand::list<T, Storage>,
Functor&& f,
bool& called,
const vtkm::cont::internal::VariantArrayHandleContainerBase& container,
Args&&... args) const
{
using DerivedArrayType = vtkm::cont::ArrayHandle<T, Storage>;
if (!called && vtkm::cont::internal::variant::IsType<DerivedArrayType>(&container))
{
called = true;
const auto* derivedContainer =
static_cast<const vtkm::cont::internal::VariantArrayHandleContainer<T>*>(&container);
DerivedArrayType derivedArray = derivedContainer->Array.template Cast<DerivedArrayType>();
VTKM_LOG_CAST_SUCC(container, derivedArray);
// If you get a compile error here, it means that you have called CastAndCall for a
// vtkm::cont::VariantArrayHandle and the arguments of the functor do not match those
// being passed. This is often because it is calling the functor with an ArrayHandle
// type that was not expected. Either add overloads to the functor to accept all
// possible array types or constrain the types tried for the CastAndCall. Note that
// the functor will be called with an array of type vtkm::cont::ArrayHandle<T, S>.
// Directly using a subclass of ArrayHandle (e.g. vtkm::cont::ArrayHandleConstant<T>)
// might not work.
f(derivedArray, std::forward<Args>(args)...);
}
}
};
struct VariantArrayHandleTryFallback
{
template <typename T, typename Functor, typename... Args>
void operator()(T,
@ -268,31 +344,74 @@ struct VariantArrayHandleTry
const auto* derived =
static_cast<const vtkm::cont::internal::VariantArrayHandleContainer<T>*>(&container);
VTKM_LOG_CAST_SUCC(container, derived);
// If you get a compile error here, it means that you have called CastAndCall for a
// vtkm::cont::VariantArrayHandle and the arguments of the functor do not match those
// being passed. This is often because it is calling the functor with an ArrayHandle
// type that was not expected. Either add overloads to the functor to accept all
// possible array types or constrain the types tried for the CastAndCall. Note that
// the functor will be called with an array of type vtkm::cont::ArrayHandle<T, S>.
// Directly using a subclass of ArrayHandle (e.g. vtkm::cont::ArrayHandleConstant<T>)
// might not work.
f(derived->Array, std::forward<Args>(args)...);
}
}
};
template <typename T>
struct IsUndefinedStorage
{
};
template <typename T, typename U>
struct IsUndefinedStorage<brigand::list<T, U>> : vtkm::cont::internal::IsInValidArrayHandle<T, U>
{
};
template <typename TypeList, typename StorageList>
struct ListTagDynamicTypes : vtkm::detail::ListRoot
{
using crossProduct = typename vtkm::ListCrossProduct<TypeList, StorageList>;
// using list = typename crossProduct::list;
using list = ::brigand::remove_if<typename crossProduct::list, IsUndefinedStorage<brigand::_1>>;
};
VTKM_CONT_EXPORT void ThrowCastAndCallException(
const vtkm::cont::internal::VariantArrayHandleContainerBase&,
const std::type_info&);
} // namespace detail
template <typename TypeList>
template <typename Functor, typename... Args>
VTKM_CONT void VariantArrayHandleBase<TypeList>::CastAndCall(Functor&& f, Args&&... args) const
template <typename StorageTagList, typename Functor, typename... Args>
VTKM_CONT void VariantArrayHandleBase<TypeList>::CastAndCallImpl(std::true_type,
StorageTagList,
Functor&& f,
Args&&... args) const
{
using crossProduct = detail::ListTagDynamicTypes<TypeList, StorageTagList>;
bool called = false;
const auto& ref = *this->ArrayContainer;
vtkm::ListForEach(detail::VariantArrayHandleTry{},
TypeList{},
crossProduct{},
std::forward<Functor>(f),
called,
ref,
std::forward<Args>(args)...);
if (!called)
{
// try to fall back to using ArrayHandleVirtual
vtkm::ListForEach(detail::VariantArrayHandleTryFallback{},
TypeList{},
std::forward<Functor>(f),
called,
ref,
std::forward<Args>(args)...);
}
if (!called)
{
// throw an exception
VTKM_LOG_CAST_FAIL(*this, TypeList);
@ -363,7 +482,7 @@ private:
public:
static VTKM_CONT void save(BinaryBuffer& bb, const Type& obj)
{
vtkm::cont::CastAndCall(obj, internal::VariantArrayHandleSerializeFunctor{}, bb);
obj.CastAndCall(vtkm::ListTagEmpty(), internal::VariantArrayHandleSerializeFunctor{}, bb);
}
static VTKM_CONT void load(BinaryBuffer& bb, Type& obj)

@ -70,8 +70,8 @@ struct ScalarFunctor
struct ArrayHandleScalarFunctor
{
template <typename T>
void operator()(const vtkm::cont::ArrayHandleVirtual<T>&) const
template <typename ArrayType>
void operator()(const ArrayType&) const
{
VTKM_TEST_FAIL("Called wrong form of functor operator.");
}

@ -95,12 +95,6 @@ public:
}
};
struct StorageListTagUnusual
: vtkm::ListTagBase<ArrayHandleWithUnusualStorage<vtkm::Id>::StorageTag,
ArrayHandleWithUnusualStorage<std::string>::StorageTag>
{
};
template <typename T>
struct TestValueFunctor
{
@ -110,10 +104,44 @@ struct TestValueFunctor
struct CheckFunctor
{
template <typename T>
void operator()(const vtkm::cont::ArrayHandleVirtual<T>& array, bool& called) const
void operator()(const vtkm::cont::ArrayHandle<T>& array,
bool& calledBasic,
bool& vtkmNotUsed(calledUnusual),
bool& vtkmNotUsed(calledVirtual)) const
{
called = true;
std::cout << " Checking for type: " << typeid(T).name() << std::endl;
calledBasic = true;
std::cout << " Checking for basic array type: " << typeid(T).name() << std::endl;
VTKM_TEST_ASSERT(array.GetNumberOfValues() == ARRAY_SIZE, "Unexpected array size.");
auto portal = array.GetPortalConstControl();
CheckPortal(portal);
}
template <typename T>
void operator()(
const vtkm::cont::ArrayHandle<T, typename ArrayHandleWithUnusualStorage<T>::StorageTag>& array,
bool& vtkmNotUsed(calledBasic),
bool& calledUnusual,
bool& vtkmNotUsed(calledVirtual)) const
{
calledUnusual = true;
std::cout << " Checking for unusual array type: " << typeid(T).name() << std::endl;
VTKM_TEST_ASSERT(array.GetNumberOfValues() == ARRAY_SIZE, "Unexpected array size.");
auto portal = array.GetPortalConstControl();
CheckPortal(portal);
}
template <typename T>
void operator()(const vtkm::cont::ArrayHandleVirtual<T>& array,
bool& vtkmNotUsed(calledBasic),
bool& vtkmNotUsed(calledUnusual),
bool& calledVirtual) const
{
calledVirtual = true;
std::cout << " Checking for virtual array type: " << typeid(T).name() << std::endl;
VTKM_TEST_ASSERT(array.GetNumberOfValues() == ARRAY_SIZE, "Unexpected array size.");
@ -132,28 +160,78 @@ void BasicArrayVariantChecks(const vtkm::cont::VariantArrayHandleBase<TypeList>&
"Dynamic array reports unexpected number of components.");
}
void CheckArrayVariant(vtkm::cont::VariantArrayHandle array, vtkm::IdComponent numComponents)
{
BasicArrayVariantChecks(array, numComponents);
bool called = false;
array.CastAndCall(CheckFunctor(), called);
VTKM_TEST_ASSERT(
called, "The functor was never called (and apparently a bad value exception not thrown).");
}
template <typename TypeList>
void CheckArrayVariant(const vtkm::cont::VariantArrayHandleBase<TypeList>& array,
vtkm::IdComponent numComponents)
vtkm::IdComponent numComponents,
bool isBasicArray,
bool isUnusualArray)
{
BasicArrayVariantChecks(array, numComponents);
bool called = false;
CastAndCall(array, CheckFunctor(), called);
std::cout << " CastAndCall with default storage" << std::endl;
bool calledBasic = false;
bool calledUnusual = false;
bool calledVirtual = false;
CastAndCall(array, CheckFunctor(), calledBasic, calledUnusual, calledVirtual);
VTKM_TEST_ASSERT(
called, "The functor was never called (and apparently a bad value exception not thrown).");
calledBasic || calledUnusual || calledVirtual,
"The functor was never called (and apparently a bad value exception not thrown).");
if (isBasicArray)
{
VTKM_TEST_ASSERT(calledBasic, "The functor was never called with the basic array fast path");
VTKM_TEST_ASSERT(!calledUnusual, "The functor was somehow called with the unusual fast path");
VTKM_TEST_ASSERT(!calledVirtual, "The functor was somehow called with the virtual path");
}
else
{
VTKM_TEST_ASSERT(!calledBasic, "The array somehow got cast to a basic storage.");
VTKM_TEST_ASSERT(!calledUnusual, "The array somehow got cast to an unusual storage.");
}
std::cout << " CastAndCall with no storage" << std::endl;
calledBasic = false;
calledUnusual = false;
calledVirtual = false;
array.CastAndCall(
vtkm::ListTagEmpty(), CheckFunctor(), calledBasic, calledUnusual, calledVirtual);
VTKM_TEST_ASSERT(
calledBasic || calledUnusual || calledVirtual,
"The functor was never called (and apparently a bad value exception not thrown).");
VTKM_TEST_ASSERT(!calledBasic, "The array somehow got cast to a basic storage.");
VTKM_TEST_ASSERT(!calledUnusual, "The array somehow got cast to an unusual storage.");
std::cout << " CastAndCall with extra storage" << std::endl;
calledBasic = false;
calledUnusual = false;
calledVirtual = false;
array.CastAndCall(vtkm::ListTagBase<vtkm::cont::StorageTagBasic,
ArrayHandleWithUnusualStorage<vtkm::Id>::StorageTag,
ArrayHandleWithUnusualStorage<std::string>::StorageTag>(),
CheckFunctor(),
calledBasic,
calledUnusual,
calledVirtual);
VTKM_TEST_ASSERT(
calledBasic || calledUnusual || calledVirtual,
"The functor was never called (and apparently a bad value exception not thrown).");
if (isBasicArray)
{
VTKM_TEST_ASSERT(calledBasic, "The functor was never called with the basic array fast path");
VTKM_TEST_ASSERT(!calledUnusual, "The functor was somehow called with the unusual fast path");
VTKM_TEST_ASSERT(!calledVirtual, "The functor was somehow called with the virtual path");
}
else if (isUnusualArray)
{
VTKM_TEST_ASSERT(calledUnusual, "The functor was never called with the unusual fast path");
VTKM_TEST_ASSERT(!calledBasic, "The functor was somehow called with the basic fast path");
VTKM_TEST_ASSERT(!calledVirtual, "The functor was somehow called with the virtual path");
}
else
{
VTKM_TEST_ASSERT(!calledBasic, "The array somehow got cast to a basic storage.");
VTKM_TEST_ASSERT(!calledUnusual, "The array somehow got cast to an unusual storage.");
}
}
template <typename T>
@ -196,7 +274,7 @@ template <typename T, typename ArrayVariantType>
void TryNewInstance(T, ArrayVariantType originalArray)
{
// This check should already have been performed by caller, but just in case.
CheckArrayVariant(originalArray, vtkm::VecTraits<T>::NUM_COMPONENTS);
CheckArrayVariant(originalArray, vtkm::VecTraits<T>::NUM_COMPONENTS, true, false);
std::cout << "Create new instance of array." << std::endl;
ArrayVariantType newArray = originalArray.NewInstance();
@ -212,7 +290,7 @@ void TryNewInstance(T, ArrayVariantType originalArray)
{
staticArray.GetPortalControl().Set(index, TestValue(index + 100, T()));
}
CheckArrayVariant(originalArray, vtkm::VecTraits<T>::NUM_COMPONENTS);
CheckArrayVariant(originalArray, vtkm::VecTraits<T>::NUM_COMPONENTS, true, false);
std::cout << "Set the new static array to expected values and make sure the new" << std::endl
<< "dynamic array points to the same new values." << std::endl;
@ -220,7 +298,7 @@ void TryNewInstance(T, ArrayVariantType originalArray)
{
staticArray.GetPortalControl().Set(index, TestValue(index, T()));
}
CheckArrayVariant(newArray, vtkm::VecTraits<T>::NUM_COMPONENTS);
CheckArrayVariant(newArray, vtkm::VecTraits<T>::NUM_COMPONENTS, true, false);
}
template <typename T>
@ -228,7 +306,7 @@ void TryDefaultType(T)
{
vtkm::cont::VariantArrayHandle array = CreateArrayVariant(T());
CheckArrayVariant(array, vtkm::VecTraits<T>::NUM_COMPONENTS);
CheckArrayVariant(array, vtkm::VecTraits<T>::NUM_COMPONENTS, true, false);
TryNewInstance(T(), array);
}
@ -240,7 +318,8 @@ struct TryBasicVTKmType
{
vtkm::cont::VariantArrayHandle array = CreateArrayVariant(T());
CheckArrayVariant(array.ResetTypes(vtkm::TypeListTagAll()), vtkm::VecTraits<T>::NUM_COMPONENTS);
CheckArrayVariant(
array.ResetTypes(vtkm::TypeListTagAll()), vtkm::VecTraits<T>::NUM_COMPONENTS, true, false);
TryNewInstance(T(), array.ResetTypes(vtkm::TypeListTagAll()));
}
@ -253,7 +332,7 @@ void TryUnusualType()
try
{
CheckArrayVariant(array, 1);
CheckArrayVariant(array, 1, true, false);
VTKM_TEST_FAIL("CastAndCall failed to error for unrecognized type.");
}
catch (vtkm::cont::ErrorBadValue&)
@ -261,7 +340,7 @@ void TryUnusualType()
std::cout << " Caught exception for unrecognized type." << std::endl;
}
CheckArrayVariant(array.ResetTypes(TypeListTagString()), 1);
CheckArrayVariant(array.ResetTypes(TypeListTagString()), 1, true, false);
std::cout << " Found type when type list was reset." << std::endl;
}
@ -271,7 +350,7 @@ void TryUnusualStorage()
try
{
CheckArrayVariant(array, 1);
CheckArrayVariant(array, 1, false, true);
}
catch (...)
{
@ -285,7 +364,7 @@ void TryUnusualTypeAndStorage()
try
{
CheckArrayVariant(array, 1);
CheckArrayVariant(array, 1, false, true);
VTKM_TEST_FAIL("CastAndCall failed to error for unrecognized type/storage.");
}
catch (vtkm::cont::ErrorBadValue&)
@ -295,7 +374,7 @@ void TryUnusualTypeAndStorage()
try
{
CheckArrayVariant(array.ResetTypes(TypeListTagString()), 1);
CheckArrayVariant(array.ResetTypes(TypeListTagString()), 1, false, true);
}
catch (...)
{

@ -852,11 +852,16 @@ private:
{
}
template <typename T>
void operator()(const vtkm::cont::ArrayHandleVirtual<T>& handle) const
template <typename ArrayHandleType>
void operator()(const ArrayHandleType& handle) const
{
VTKM_IS_ARRAY_HANDLE(ArrayHandleType);
using T = typename ArrayHandleType::ValueType;
if (this->Permutation.GetNumberOfValues() < 1)
{
return;
}
vtkm::cont::ArrayHandle<T> out;
out.Allocate(this->Permutation.GetNumberOfValues());