Use tinyusdz-customized linb::any.

This commit is contained in:
Syoyo Fujita
2022-07-21 22:20:13 +09:00
parent 0967b7561b
commit 795a4509d9
11 changed files with 786 additions and 98 deletions

View File

@@ -277,3 +277,4 @@ TinyUSDZ is licensed under MIT license.
* formatxx: unlicense. https://github.com/seanmiddleditch/formatxx
* mapbox/variant: BSD-3 or BSL-1.0 license. https://github.com/mapbox/variant
* ubench.h: Unlicense. https://github.com/sheredom/ubench.h
* thelink2012/any : BSL-1.0 license. https://github.com/thelink2012/any

View File

@@ -9,16 +9,36 @@ UBENCH(perf, vector_double_push_back_10M)
constexpr size_t niter = 10 * 10000;
for (size_t i = 0; i < niter; i++) {
v.push_back(double(i));
}
}
}
UBENCH(perf, any_value_double_10M)
{
constexpr size_t niter = 10 * 10000;
for (size_t i = 0; i < niter; i++) {
tinyusdz::value::any_value a;
linb::any a;
a = double(i);
}
}
}
UBENCH(perf, thelink2012_any_float_10M)
{
constexpr size_t niter = 10 * 10000;
for (size_t i = 0; i < niter; i++) {
linb::any a;
a = float(i);
}
}
UBENCH(perf, thelink2012_any_double_10M)
{
constexpr size_t niter = 10 * 10000;
std::vector<linb::any> v;
for (size_t i = 0; i < niter; i++) {
v.push_back(double(i));
}
}
#if 0
@@ -28,7 +48,7 @@ UBENCH(perf, any_value_100M)
for (size_t i = 0; i < niter; i++) {
tinyusdz::value::any_value a;
a = i;
}
}
}
#endif
@@ -41,7 +61,7 @@ UBENCH(perf, timesamples_double_10M)
for (size_t i = 0; i < ns; i++) {
ts.times.push_back(double(i));
ts.values.push_back(double(i));
}
}
}
//int main(int argc, char **argv)

View File

@@ -438,8 +438,9 @@ class CrateValue {
// Undefined behavior(usually will triger segmentation fault) when
// type-mismatch. (We don't throw exception)
template <class T>
const T &value() const {
return (*reinterpret_cast<const T *>(value_.value()));
const T value() const {
//return (*reinterpret_cast<const T *>(value_.value()));
return linb::any_cast<const T>(value_);
}
// Type-safe way to get concrete value.
@@ -456,7 +457,8 @@ class CrateValue {
}
private:
value::any_value value_;
// TODO: Use value::Value?
linb::any value_;
};

View File

@@ -84,11 +84,20 @@ struct PrimVar {
}
if (value::TypeTrait<T>::type_id == var.values[0].type_id()) {
return std::move(*reinterpret_cast<const T *>(var.values[0].value()));
//return std::move(*reinterpret_cast<const T *>(var.values[0].value()));
auto pv = linb::any_cast<const T>(&var.values[0]);
if (pv) {
return (*pv);
}
return nonstd::nullopt;
} else if (value::TypeTrait<T>::underlying_type_id == var.values[0].underlying_type_id()) {
// `roll` type. Can be able to cast to underlying type since the memory
// layout does not change.
return *reinterpret_cast<const T *>(var.values[0].value());
//return *reinterpret_cast<const T *>(var.values[0].value());
if (auto pv = linb::any_cast<const T>(&var.values[0])) {
return (*pv);
}
return nonstd::nullopt;
}
return nonstd::nullopt;
}

618
src/tiny-any.inc Normal file
View File

@@ -0,0 +1,618 @@
//
// Implementation of N4562 std::experimental::any (merged into C++17) for C++11 compilers.
//
// See also:
// + http://en.cppreference.com/w/cpp/any
// + http://en.cppreference.com/w/cpp/experimental/any
// + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#any
// + https://cplusplus.github.io/LWG/lwg-active.html#2509
//
//
// Copyright (c) 2016 Denilson das Mercês Amorim
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// TinyUSDZ modification
// - Disable exceptions, RTTI
// - Use type_id with TypeTrait<T>::type_id
// - Use type_name with TypeTrait<T>::type_name
// - Assume this tiny-any.inc is included inside value-type.hh (since TypeTrait<T> implementations are required)
//
#ifndef LINB_ANY_HPP
#define LINB_ANY_HPP
#pragma once
//#include <typeinfo>
#include <type_traits>
//#include <stdexcept>
#include <utility>
#include <cstdint>
#if 0
//#include "value-type.hh"
namespace tinyusdz {
namespace value {
// Forward decl.
template<typename dtype>
class TypeTrait;
} // namespace value
} // namespace tinyusdz
#endif
#ifndef ANY_IMPL_NO_EXCEPTIONS
#define ANY_IMPL_NO_EXCEPTIONS
#endif
#ifndef ANY_IMPL_NO_RTTI
#define ANY_IMPL_NO_RTTI
#endif
#if defined(PARTICLE)
#if !defined(__cpp_exceptions) && !defined(ANY_IMPL_NO_EXCEPTIONS) && !defined(ANY_IMPL_EXCEPTIONS)
# define ANY_IMPL_NO_EXCEPTIONS
# endif
#else
// you can opt-out of exceptions by definining ANY_IMPL_NO_EXCEPTIONS,
// but you must ensure not to cast badly when passing an `any' object to any_cast<T>(any)
#endif
#if defined(PARTICLE)
#if !defined(__cpp_rtti) && !defined(ANY_IMPL_NO_RTTI) && !defined(ANY_IMPL_RTTI)
# define ANY_IMPL_NO_RTTI
# endif
#else
// you can opt-out of RTTI by defining ANY_IMPL_NO_RTTI,
// in order to disable functions working with the typeid of a type
#endif
namespace linb
{
#ifndef ANY_IMPL_NO_EXCEPTIONS
class bad_any_cast : public std::bad_cast
{
public:
const char* what() const noexcept override
{
return "bad any cast";
}
};
#endif
class any final
{
public:
/// Constructs an object of type any with an empty state.
any() :
vtable(nullptr)
{
}
/// Constructs an object of type any with an equivalent state as other.
any(const any& rhs) :
vtable(rhs.vtable)
{
if(!rhs.empty())
{
rhs.vtable->copy(rhs.storage, this->storage);
}
}
/// Constructs an object of type any with a state equivalent to the original state of other.
/// rhs is left in a valid but otherwise unspecified state.
any(any&& rhs) noexcept :
vtable(rhs.vtable)
{
if(!rhs.empty())
{
rhs.vtable->move(rhs.storage, this->storage);
rhs.vtable = nullptr;
}
}
/// Same effect as this->clear().
~any()
{
this->clear();
}
/// Constructs an object of type any that contains an object of type T direct-initialized with std::forward<ValueType>(value).
///
/// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed.
/// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed.
template<typename ValueType, typename = typename std::enable_if<!std::is_same<typename std::decay<ValueType>::type, any>::value>::type>
any(ValueType&& value)
{
static_assert(std::is_copy_constructible<typename std::decay<ValueType>::type>::value,
"T shall satisfy the CopyConstructible requirements.");
this->construct(std::forward<ValueType>(value));
}
/// Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown.
any& operator=(const any& rhs)
{
any(rhs).swap(*this);
return *this;
}
/// Has the same effect as any(std::move(rhs)).swap(*this).
///
/// The state of *this is equivalent to the original state of rhs and rhs is left in a valid
/// but otherwise unspecified state.
any& operator=(any&& rhs) noexcept
{
any(std::move(rhs)).swap(*this);
return *this;
}
/// Has the same effect as any(std::forward<ValueType>(value)).swap(*this). No effect if a exception is thrown.
///
/// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed.
/// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed.
template<typename ValueType, typename = typename std::enable_if<!std::is_same<typename std::decay<ValueType>::type, any>::value>::type>
any& operator=(ValueType&& value)
{
static_assert(std::is_copy_constructible<typename std::decay<ValueType>::type>::value,
"T shall satisfy the CopyConstructible requirements.");
any(std::forward<ValueType>(value)).swap(*this);
return *this;
}
/// If not empty, destroys the contained object.
void clear() noexcept
{
if(!empty())
{
this->vtable->destroy(storage);
this->vtable = nullptr;
}
}
/// Returns true if *this has no contained object, otherwise false.
bool empty() const noexcept
{
return this->vtable == nullptr;
}
#ifndef ANY_IMPL_NO_RTTI
/// If *this has a contained object of type T, typeid(T); otherwise typeid(void).
const std::type_info& type() const noexcept
{
return empty()? typeid(void) : this->vtable->type();
}
#endif
#if 1 // tinyusdz
uint32_t type_id() const noexcept
{
return empty()? tinyusdz::value::TypeTrait<void>::type_id : this->vtable->type_id();
}
uint32_t underlying_type_id() const noexcept
{
return empty()? tinyusdz::value::TypeTrait<void>::underlying_type_id : this->vtable->underlying_type_id();
}
const std::string type_name() const noexcept
{
return empty()? tinyusdz::value::TypeTrait<void>::type_name() : this->vtable->type_name();
}
const std::string underlying_type_name() const noexcept
{
return empty()? tinyusdz::value::TypeTrait<void>::underlying_type_name() : this->vtable->underlying_type_name();
}
#endif
/// Exchange the states of *this and rhs.
void swap(any& rhs) noexcept
{
if(this->vtable != rhs.vtable)
{
any tmp(std::move(rhs));
// move from *this to rhs.
rhs.vtable = this->vtable;
if(this->vtable != nullptr)
{
this->vtable->move(this->storage, rhs.storage);
//this->vtable = nullptr; -- unneeded, see below
}
// move from tmp (previously rhs) to *this.
this->vtable = tmp.vtable;
if(tmp.vtable != nullptr)
{
tmp.vtable->move(tmp.storage, this->storage);
tmp.vtable = nullptr;
}
}
else // same types
{
if(this->vtable != nullptr)
this->vtable->swap(this->storage, rhs.storage);
}
}
private: // Storage and Virtual Method Table
union storage_union
{
using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of<void*>::value>::type;
void* dynamic;
stack_storage_t stack; // 2 words for e.g. shared_ptr
};
/// Base VTable specification.
struct vtable_type
{
// Note: The caller is responssible for doing .vtable = nullptr after destructful operations
// such as destroy() and/or move().
#ifndef ANY_IMPL_NO_RTTI
/// The type of the object this vtable is for.
const std::type_info& (*type)() noexcept;
#endif
#if 1
uint32_t (*type_id)() noexcept;
uint32_t (*underlying_type_id)() noexcept;
const std::string (*type_name)() noexcept;
const std::string (*underlying_type_name)() noexcept;
#endif
/// Destroys the object in the union.
/// The state of the union after this call is unspecified, caller must ensure not to use src anymore.
void(*destroy)(storage_union&) noexcept;
/// Copies the **inner** content of the src union into the yet unitialized dest union.
/// As such, both inner objects will have the same state, but on separate memory locations.
void(*copy)(const storage_union& src, storage_union& dest);
/// Moves the storage from src to the yet unitialized dest union.
/// The state of src after this call is unspecified, caller must ensure not to use src anymore.
void(*move)(storage_union& src, storage_union& dest) noexcept;
/// Exchanges the storage between lhs and rhs.
void(*swap)(storage_union& lhs, storage_union& rhs) noexcept;
};
/// VTable for dynamically allocated storage.
template<typename T>
struct vtable_dynamic
{
#ifndef ANY_IMPL_NO_RTTI
static const std::type_info& type() noexcept
{
return typeid(T);
}
#endif
#if 1 // tinyusdz
static uint32_t type_id() noexcept
{
return tinyusdz::value::TypeTrait<T>::type_id;
}
static uint32_t underlying_type_id() noexcept
{
return tinyusdz::value::TypeTrait<T>::underlying_type_id;
}
static const std::string type_name() noexcept
{
return tinyusdz::value::TypeTrait<T>::type_name();
}
static const std::string underlying_type_name() noexcept
{
return tinyusdz::value::TypeTrait<T>::underlying_type_name();
}
#endif
static void destroy(storage_union& storage) noexcept
{
//assert(reinterpret_cast<T*>(storage.dynamic));
delete reinterpret_cast<T*>(storage.dynamic);
}
static void copy(const storage_union& src, storage_union& dest)
{
dest.dynamic = new T(*reinterpret_cast<const T*>(src.dynamic));
}
static void move(storage_union& src, storage_union& dest) noexcept
{
dest.dynamic = src.dynamic;
src.dynamic = nullptr;
}
static void swap(storage_union& lhs, storage_union& rhs) noexcept
{
// just exchage the storage pointers.
std::swap(lhs.dynamic, rhs.dynamic);
}
};
/// VTable for stack allocated storage.
template<typename T>
struct vtable_stack
{
#ifndef ANY_IMPL_NO_RTTI
static const std::type_info& type() noexcept
{
return typeid(T);
}
#endif
#if 1 // tinyusdz
static uint32_t type_id() noexcept
{
return tinyusdz::value::TypeTrait<T>::type_id;
}
static uint32_t underlying_type_id() noexcept
{
return tinyusdz::value::TypeTrait<T>::underlying_type_id;
}
static const std::string type_name() noexcept
{
return tinyusdz::value::TypeTrait<T>::type_name();
}
static const std::string underlying_type_name() noexcept
{
return tinyusdz::value::TypeTrait<T>::underlying_type_name();
}
#endif
static void destroy(storage_union& storage) noexcept
{
reinterpret_cast<T*>(&storage.stack)->~T();
}
static void copy(const storage_union& src, storage_union& dest)
{
new (&dest.stack) T(reinterpret_cast<const T&>(src.stack));
}
static void move(storage_union& src, storage_union& dest) noexcept
{
// one of the conditions for using vtable_stack is a nothrow move constructor,
// so this move constructor will never throw a exception.
new (&dest.stack) T(std::move(reinterpret_cast<T&>(src.stack)));
destroy(src);
}
static void swap(storage_union& lhs, storage_union& rhs) noexcept
{
storage_union tmp_storage;
move(rhs, tmp_storage);
move(lhs, rhs);
move(tmp_storage, lhs);
}
};
/// Whether the type T must be dynamically allocated or can be stored on the stack.
template<typename T>
struct requires_allocation :
std::integral_constant<bool,
!(std::is_nothrow_move_constructible<T>::value // N4562 §6.3/3 [any.class]
&& sizeof(T) <= sizeof(storage_union::stack)
&& std::alignment_of<T>::value <= std::alignment_of<storage_union::stack_storage_t>::value)>
{};
/// Returns the pointer to the vtable of the type T.
template<typename T>
static vtable_type* vtable_for_type()
{
using VTableType = typename std::conditional<requires_allocation<T>::value, vtable_dynamic<T>, vtable_stack<T>>::type;
static vtable_type table = {
#ifndef ANY_IMPL_NO_RTTI
VTableType::type,
#endif
#if 1
VTableType::type_id,
VTableType::underlying_type_id,
VTableType::type_name,
VTableType::underlying_type_name,
#endif
VTableType::destroy,
VTableType::copy, VTableType::move,
VTableType::swap,
};
return &table;
}
protected:
template<typename T>
friend const T* any_cast(const any* operand) noexcept;
template<typename T>
friend T* any_cast(any* operand) noexcept;
#ifndef ANY_IMPL_NO_RTTI
/// Same effect as is_same(this->type(), t);
bool is_typed(const std::type_info& t) const
{
return is_same(this->type(), t);
}
#endif
#ifndef ANY_IMPL_NO_RTTI
/// Checks if two type infos are the same.
///
/// If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the
/// type infos, otherwise does an actual comparision. Checking addresses is
/// only a valid approach when there's no interaction with outside sources
/// (other shared libraries and such).
static bool is_same(const std::type_info& a, const std::type_info& b)
{
#ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE
return &a == &b;
#else
return a == b;
#endif
}
#endif
/// Casts (with no type_info checks) the storage pointer as const T*.
template<typename T>
const T* cast() const noexcept
{
return requires_allocation<typename std::decay<T>::type>::value?
reinterpret_cast<const T*>(storage.dynamic) :
reinterpret_cast<const T*>(&storage.stack);
}
/// Casts (with no type_info checks) the storage pointer as T*.
template<typename T>
T* cast() noexcept
{
return requires_allocation<typename std::decay<T>::type>::value?
reinterpret_cast<T*>(storage.dynamic) :
reinterpret_cast<T*>(&storage.stack);
}
private:
storage_union storage; // on offset(0) so no padding for align
vtable_type* vtable;
template<typename ValueType, typename T>
typename std::enable_if<requires_allocation<T>::value>::type
do_construct(ValueType&& value)
{
storage.dynamic = new T(std::forward<ValueType>(value));
}
template<typename ValueType, typename T>
typename std::enable_if<!requires_allocation<T>::value>::type
do_construct(ValueType&& value)
{
new (&storage.stack) T(std::forward<ValueType>(value));
}
/// Chooses between stack and dynamic allocation for the type decay_t<ValueType>,
/// assigns the correct vtable, and constructs the object on our storage.
template<typename ValueType>
void construct(ValueType&& value)
{
using T = typename std::decay<ValueType>::type;
this->vtable = vtable_for_type<T>();
do_construct<ValueType,T>(std::forward<ValueType>(value));
}
};
namespace detail
{
template<typename ValueType>
inline ValueType any_cast_move_if_true(typename std::remove_reference<ValueType>::type* p, std::true_type)
{
return std::move(*p);
}
template<typename ValueType>
inline ValueType any_cast_move_if_true(typename std::remove_reference<ValueType>::type* p, std::false_type)
{
return *p;
}
}
/// Performs *any_cast<add_const_t<remove_reference_t<ValueType>>>(&operand), or throws bad_any_cast on failure.
template<typename ValueType>
inline ValueType any_cast(const any& operand)
{
auto p = any_cast<typename std::add_const<typename std::remove_reference<ValueType>::type>::type>(&operand);
#ifndef ANY_IMPL_NO_EXCEPTIONS
if(p == nullptr) throw bad_any_cast();
#endif
return *p;
}
/// Performs *any_cast<remove_reference_t<ValueType>>(&operand), or throws bad_any_cast on failure.
template<typename ValueType>
inline ValueType any_cast(any& operand)
{
auto p = any_cast<typename std::remove_reference<ValueType>::type>(&operand);
#ifndef ANY_IMPL_NO_EXCEPTIONS
if(p == nullptr) throw bad_any_cast();
#endif
return *p;
}
///
/// If ValueType is MoveConstructible and isn't a lvalue reference, performs
/// std::move(*any_cast<remove_reference_t<ValueType>>(&operand)), otherwise
/// *any_cast<remove_reference_t<ValueType>>(&operand). Throws bad_any_cast on failure.
///
template<typename ValueType>
inline ValueType any_cast(any&& operand)
{
using can_move = std::integral_constant<bool,
std::is_move_constructible<ValueType>::value
&& !std::is_lvalue_reference<ValueType>::value>;
auto p = any_cast<typename std::remove_reference<ValueType>::type>(&operand);
#ifndef ANY_IMPL_NO_EXCEPTIONS
if(p == nullptr) throw bad_any_cast();
#endif
return detail::any_cast_move_if_true<ValueType>(p, can_move());
}
/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object
/// contained by operand, otherwise nullptr.
template<typename ValueType>
inline const ValueType* any_cast(const any* operand) noexcept
{
using T = typename std::decay<ValueType>::type;
#ifndef ANY_IMPL_NO_RTTI
if (operand && operand->is_typed(typeid(T)))
#else
if (operand && operand->vtable == any::vtable_for_type<T>())
#endif
return operand->cast<ValueType>();
else
return nullptr;
}
/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object
/// contained by operand, otherwise nullptr.
template<typename ValueType>
inline ValueType* any_cast(any* operand) noexcept
{
using T = typename std::decay<ValueType>::type;
#ifndef ANY_IMPL_NO_RTTI
if (operand && operand->is_typed(typeid(T)))
#else
if (operand && operand->vtable == any::vtable_for_type<T>())
#endif
return operand->cast<ValueType>();
else
return nullptr;
}
}
namespace std
{
inline void swap(linb::any& lhs, linb::any& rhs) noexcept
{
lhs.swap(rhs);
}
}
#endif

View File

@@ -11,6 +11,8 @@
// so an application should not frequently construct Token class
// among threads.
//
// TODO: Deprecate `Token` class? Seems there is no beneficial usecase in TinyUSDZ(`std::string` may suffice)
//
#include <iostream>
#include <string>

View File

@@ -1,5 +1,8 @@
// SPDX-License-Identifier: MIT
// Copyright 2022 - Present, Syoyo Fujita.
#include <sstream>
#include "value-pprint.hh"
#include "prim-types.hh"
@@ -222,7 +225,7 @@ std::ostream &operator<<(std::ostream &ofs, const tinyusdz::value::token &tok) {
std::ostream &operator<<(std::ostream &ofs, const tinyusdz::value::dict &m) {
ofs << "{\n";
for (const auto &item : m) {
ofs << item.first << " = " << item.second << "\n";
ofs << item.first << " = " << tinyusdz::value::pprint_any(item.second) << "\n";
}
ofs << "}";
@@ -234,26 +237,30 @@ std::ostream &operator<<(std::ostream &ofs, const tinyusdz::value::dict &m) {
namespace tinyusdz {
namespace value {
std::ostream &operator<<(std::ostream &os, const any_value &v) {
//std::ostream &operator<<(std::ostream &os, const any_value &v) {
//std::ostream &operator<<(std::ostream &os, const linb::any &v) {
std::string pprint_any(const linb::any &v) {
std::stringstream os;
// Simple brute-force way..
// TODO: Use std::function or some template technique?
#define BASETYPE_CASE_EXPR(__ty) \
case TypeTrait<__ty>::type_id: { \
os << *reinterpret_cast<const __ty *>(v.value()); \
os << linb::any_cast<const __ty>(v); \
break; \
}
#define ARRAY1DTYPE_CASE_EXPR(__ty) \
case TypeTrait<std::vector<__ty>>::type_id: { \
os << *reinterpret_cast<const std::vector<__ty> *>(v.value()); \
os << linb::any_cast<const std::vector<__ty>>(v); \
break; \
}
#define ARRAY2DTYPE_CASE_EXPR(__ty) \
case TypeTrait<std::vector<std::vector<__ty>>>::type_id: { \
os << *reinterpret_cast<const std::vector<std::vector<__ty>> *>( \
v.value()); \
os << linb::any_cast<const std::vector<std::vector<__ty>>>(v); \
break; \
}
@@ -329,7 +336,7 @@ std::ostream &operator<<(std::ostream &os, const any_value &v) {
#undef ARRAY2DTYPE_CASE_EXPR
#undef CASE_EXPR_LIST
return os;
return os.str();
}

View File

@@ -83,7 +83,9 @@ namespace tinyusdz {
namespace value {
//std::ostream &operator<<(std::ostream &os, const tinyusdz::value::Value &v);
std::ostream &operator<<(std::ostream &os, const tinyusdz::value::any_value &v);
//std::ostream &operator<<(std::ostream &os, const tinyusdz::value::any_value &v);
//std::ostream &operator<<(std::ostream &os, const linb::any &v);
std::string pprint_any(const linb::any &v);
} // namespace primvar
} // namespace tinyusdz

View File

@@ -7,9 +7,10 @@
namespace tinyusdz {
namespace value {
base_value::~base_value() {}
//base_value::~base_value() {}
#if 0
bool is_float(const any_value &v) {
if (v.underlying_type_name() == "float") {
return true;
@@ -89,6 +90,7 @@ bool is_double4(const Value &v) {
return false;
}
#endif
#if 0 // TODO: Remove
bool Reconstructor::reconstruct(AttribMap &amap) {

View File

@@ -1,13 +1,15 @@
/// Copyright 2021-present Syoyo Fujita.
/// MIT license.
///
/// Type-erasure technique for Value, a Value class which can represent USD's mandatory and frequently used types(e.g. `float3`, `token`, `asset`)
/// and its array and compound-types(1D/2D array, dictionary).
/// Neigher std::any nor std::variant is applicable for such usecases, so write our own.
/// Type-erasure technique for Value, a Value class which can represent USD's
/// mandatory and frequently used types(e.g. `float3`, `token`, `asset`) and its
/// array and compound-types(1D/2D array, dictionary). Neigher std::any nor
/// std::variant is applicable for such usecases, so write our own.
///
#pragma once
#include <array>
#include <cmath>
#include <cstring>
#include <functional>
#include <iostream>
@@ -15,7 +17,6 @@
#include <memory>
#include <type_traits>
#include <vector>
#include <cmath>
#ifdef __clang__
#pragma clang diagnostic push
@@ -49,7 +50,11 @@
#endif
#include "token-type.hh"
//#include "external/staticstruct.hh"
// forward decl
namespace linb {
class any;
};
namespace tinyusdz {
namespace value {
@@ -59,7 +64,7 @@ namespace value {
constexpr char kToken[] = "token";
constexpr char kString[] = "string";
constexpr char kPath[] = "Path";
constexpr char kAssetPath[] = "asset"; // `asset` in USDA
constexpr char kAssetPath[] = "asset"; // `asset` in USDA
constexpr char kDictionary[] = "dictionary";
constexpr char kTimeCode[] = "timecode";
@@ -87,12 +92,12 @@ constexpr char kMatrix2d[] = "matrix2d";
constexpr char kMatrix3d[] = "matrix3d";
constexpr char kMatrix4d[] = "matrix4d";
constexpr char kFloat[] = "float";
constexpr char kFloat[] = "float";
constexpr char kFloat2[] = "float2";
constexpr char kFloat3[] = "float3";
constexpr char kFloat4[] = "float4";
constexpr char kDouble[] = "double";
constexpr char kDouble[] = "double";
constexpr char kDouble2[] = "double2";
constexpr char kDouble3[] = "double3";
constexpr char kDouble4[] = "double4";
@@ -130,19 +135,17 @@ constexpr char kTexCoord3d[] = "texCoord3d";
constexpr char kRelationship[] = "rel";
inline std::string Add1DArraySuffix(const std::string &c) {
return c + "[]";
}
inline std::string Add1DArraySuffix(const std::string &c) { return c + "[]"; }
using token = tinyusdz::Token;
// SdfAssetPath
class asset_path
{
public:
asset_path() = default;
asset_path(const std::string &a) : asset_path_(a) {}
asset_path(const std::string &a, const std::string &r) : asset_path_(a), resolved_path_(r) {}
class asset_path {
public:
asset_path() = default;
asset_path(const std::string &a) : asset_path_(a) {}
asset_path(const std::string &a, const std::string &r)
: asset_path_(a), resolved_path_(r) {}
private:
std::string asset_path_;
@@ -158,17 +161,17 @@ class asset_path
// (See `crate-format.hh` for Type ID used in Crate binary)
//
// TODO(syoyo): Support 3D and 4D?
constexpr uint32_t TYPE_ID_1D_ARRAY_BIT = 1 << 20; // 1024
constexpr uint32_t TYPE_ID_2D_ARRAY_BIT = 1 << 21; // 2048
//constexpr uint32_t TYPE_ID_3D_ARRAY_BIT = 1 << 22;
//constexpr uint32_t TYPE_ID_4D_ARRAY_BIT = 1 << 23;
constexpr uint32_t TYPE_ID_1D_ARRAY_BIT = 1 << 20; // 1024
constexpr uint32_t TYPE_ID_2D_ARRAY_BIT = 1 << 21; // 2048
// constexpr uint32_t TYPE_ID_3D_ARRAY_BIT = 1 << 22;
// constexpr uint32_t TYPE_ID_4D_ARRAY_BIT = 1 << 23;
enum TypeId {
TYPE_ID_INVALID, // = 0
TYPE_ID_NULL,
TYPE_ID_VOID,
TYPE_ID_MONOSTATE,
TYPE_ID_BLOCK, // None as type
TYPE_ID_BLOCK, // None as type
TYPE_ID_TOKEN,
TYPE_ID_STRING,
@@ -248,11 +251,11 @@ enum TypeId {
TYPE_ID_PAYLOAD,
TYPE_ID_TIMECODE,
//TYPE_ID_TIMESAMPLE,
// TYPE_ID_TIMESAMPLE,
TYPE_ID_DICT,
//TYPE_ID_ASSET,
// TYPE_ID_ASSET,
TYPE_ID_ASSET_PATH,
// Types in prim-types.hh
@@ -324,8 +327,7 @@ enum TypeId {
TYPE_ID_ALL = (TYPE_ID_2D_ARRAY_BIT - 1) // terminator.
};
struct timecode
{
struct timecode {
double value;
};
@@ -617,16 +619,15 @@ struct texcoord3d {
};
// Attribute Block(None)
struct Block {
};
struct Block {};
using double2 = std::array<double, 2>;
using double3 = std::array<double, 3>;
using double4 = std::array<double, 4>;
struct any_value;
using dict = std::map<std::string, any_value>;
//struct any_value;
//using dict = std::map<std::string, any_value>;
using dict = std::map<std::string, linb::any>;
template <class dtype>
struct TypeTrait;
@@ -697,7 +698,6 @@ DEFINE_TYPE_TRAIT(double2, kDouble2, TYPE_ID_DOUBLE2, 2);
DEFINE_TYPE_TRAIT(double3, kDouble3, TYPE_ID_DOUBLE3, 3);
DEFINE_TYPE_TRAIT(double4, kDouble4, TYPE_ID_DOUBLE4, 4);
DEFINE_TYPE_TRAIT(quath, kQuath, TYPE_ID_QUATH, 1);
DEFINE_TYPE_TRAIT(quatf, kQuatf, TYPE_ID_QUATF, 1);
DEFINE_TYPE_TRAIT(quatd, kQuatd, TYPE_ID_QUATD, 1);
@@ -748,7 +748,8 @@ DEFINE_TYPE_TRAIT(dict, kDictionary, TYPE_ID_DICT, 1);
DEFINE_TYPE_TRAIT(asset_path, kAssetPath, TYPE_ID_ASSET_PATH, 1);
//
// Other types(e.g. TYPE_ID_REFERENCE) are defined in `prim-types.hh` and `crate-format.hh`(Data types used in Crate data)
// Other types(e.g. TYPE_ID_REFERENCE) are defined in `prim-types.hh` and
// `crate-format.hh`(Data types used in Crate data)
//
#undef DEFINE_TYPE_TRAIT
@@ -791,6 +792,7 @@ struct TypeTrait<std::vector<std::vector<T>>> {
nonstd::optional<std::string> TryGetTypeName(uint32_t tyid);
std::string GetTypeName(uint32_t tyid);
#if 0 // TODO: Remove
struct base_value {
virtual ~base_value();
virtual const std::string type_name() const = 0;
@@ -918,20 +920,34 @@ struct any_value {
return *(reinterpret_cast<const std::vector<T> *>(p->value()));
}
std::shared_ptr<base_value> p; // TODO: Use raw pointer?
std::shared_ptr<base_value> p; // TODO: Use raw pointer?
};
#endif
} // namespace value
} // namespace tinyusdz
// TODO(syoyo): Replace any_value with linb::any
// TODO(syoyo): Move TypeTrait<T> code to another header to simplify .inc
// inclusion.
#include "tiny-any.inc"
namespace tinyusdz {
namespace value {
// Handy, but may not efficient for large time samples(e.g. 1M samples or more)
//
// For the runtime speed, adding 10M `double` samples to any_value takes roughly 3.1 ms on
// Threadripper 1950X, whereas simple vector<double> push_back only takes 390 us(roughly x8 times faster).
// (Build benchmarks to see the numbers on your CPU)
// For the runtime speed, with "-O2 -g" optimization, adding 10M `double`
// samples to linb::any takes roughly 1.8 ms on Threadripper 1950X, whereas
// simple vector<double> push_back takes 390 us(roughly x4 times faster). (Build
// benchmarks to see the numbers on your CPU)
//
// We assume having large time samples is rare situlation, but will do some C++ code optimization if required.
// We assume having large time samples is rare situlation, but will do some C++
// code optimization if required.
//
struct TimeSamples {
std::vector<double> times;
std::vector<any_value> values; // Could be an array of 'None' or Type T
std::vector<linb::any> values; // Could be an array of 'None' or Type T
bool Valid() {
if (times.size() > 0) {
@@ -942,9 +958,8 @@ struct TimeSamples {
};
// simple linear interpolator
template<typename T>
struct LinearInterpolator
{
template <typename T>
struct LinearInterpolator {
static T interpolate(const T *values, const size_t n, const double _t) {
if (n == 0) {
return static_cast<T>(0);
@@ -955,8 +970,8 @@ struct LinearInterpolator
// [0.0, 1.0]
double t = std::fmin(0.0, std::fmax(_t, 1.0));
size_t idx0 = std::max(n-1, size_t(t * double(n)));
size_t idx1 = std::max(n-1, idx0+1);
size_t idx0 = std::max(n - 1, size_t(t * double(n)));
size_t idx1 = std::max(n - 1, idx0 + 1);
return (1.0 - t) * values[idx0] + t * values[idx1];
}
@@ -973,23 +988,21 @@ struct LinearInterpolator
//
// radius = { 0: 1.0, 2: 3.0 }
//
template<typename T>
struct AnimatableValue
{
std::vector<double> times; // Assume sorted
template <typename T>
struct AnimatableValue {
std::vector<double> times; // Assume sorted
std::vector<T> values;
bool is_scalar() const {
return (times.size() == 0) && (values.size() == 1);
}
bool is_scalar() const { return (times.size() == 0) && (values.size() == 1); }
bool is_timesample() const {
return (times.size() > 0) && (times.size() == values.size());
}
template<class Interpolator>
template <class Interpolator>
T Get(double time = 0.0) {
std::vector<double>::iterator it = std::lower_bound(times.begin(), times.end(), time);
std::vector<double>::iterator it =
std::lower_bound(times.begin(), times.end(), time);
size_t idx0, idx1;
if (it != times.end()) {
@@ -1010,6 +1023,7 @@ struct AnimatableValue
}
};
#if 0 // Remove
struct PrimVar {
// For scalar value, times.size() == 0, and values.size() == 1
TimeSamples var;
@@ -1048,11 +1062,13 @@ struct PrimVar {
}
if (TypeTrait<T>::type_id == var.values[0].type_id()) {
return std::move(*reinterpret_cast<const T *>(var.values[0].value()));
//return std::move(*reinterpret_cast<const T *>(var.values[0].value()));
return std::move(linb::any_cast<const T *>(var.values[0]));
} else if (TypeTrait<T>::underlying_type_id == var.values[0].underlying_type_id()) {
// `roll` type. Can be able to cast to underlying type since the memory
// layout does not change.
return std::move(*reinterpret_cast<const T *>(var.values[0].value()));
//return std::move(*reinterpret_cast<const T *>(var.values[0].value()));
return std::move(linb::any_cast<const T *>(var.values[0]));
}
return nonstd::nullopt;
}
@@ -1066,9 +1082,11 @@ struct PrimVar {
}
};
#endif
// using Object = std::map<std::string, any_value>;
///
/// Generic Value class using any
///
class Value {
public:
// using Dict = std::map<std::string, Value>;
@@ -1078,8 +1096,8 @@ class Value {
template <class T>
Value(const T &v) : v_(v) {}
//template <class T>
//Value(T &&v) : v_(v) {}
// template <class T>
// Value(T &&v) : v_(v) {}
std::string type_name() const { return v_.type_name(); }
std::string underlying_type_name() const { return v_.underlying_type_name(); }
@@ -1091,7 +1109,8 @@ class Value {
template <class T>
const T *as() const {
if (TypeTrait<T>::type_id == v_.type_id()) {
return reinterpret_cast<const T *>(v_.value());
//return reinterpret_cast<const T *>(v_.value());
return linb::any_cast<const T>(&v_);
} else {
return nullptr;
}
@@ -1101,8 +1120,9 @@ class Value {
// Undefined behavior(usually will triger segmentation fault) when
// type-mismatch. (We don't throw exception)
template <class T>
const T &value() const {
return (*reinterpret_cast<const T *>(v_.value()));
const T value() const {
//return (*reinterpret_cast<const T *>(v_.value()));
return linb::any_cast<const T>(v_);
}
// Type-safe way to get concrete value.
@@ -1124,24 +1144,23 @@ class Value {
return (*this);
}
bool is_array() const { return v_.ndim() > 0; }
int32_t ndim() const { return v_.ndim(); }
//bool is_array() const { return v_.ndim() > 0; }
//int32_t ndim() const { return v_.ndim(); }
uint32_t ncomp() const { return v_.ncomp(); }
//uint32_t ncomp() const { return v_.ncomp(); }
bool is_vector_type() const { return v_.ncomp() > 1; }
//bool is_vector_type() const { return v_.ncomp() > 1; }
//friend std::ostream &operator<<(std::ostream &os, const Value &v);
const any_value& get_raw() const {
return v_;
}
// friend std::ostream &operator<<(std::ostream &os, const Value &v);
//const any_value &get_raw() const { return v_; }
private:
any_value v_;
//any_value v_;
linb::any v_;
};
bool is_float(const any_value &v);
bool is_double(const any_value &v);
//bool is_float(const any_value &v);
//bool is_double(const any_value &v);
// Frequently-used utility function
bool is_float(const Value &v);
@@ -1153,7 +1172,7 @@ bool is_double2(const Value &v);
bool is_double3(const Value &v);
bool is_double4(const Value &v);
#if 0 // TODO: Remove? since not used so frequently at the moment.
//
// typecast from type_id
// It does not throw exception.
@@ -1185,7 +1204,9 @@ TYPECAST_BASETYPE(TYPE_ID_FLOAT | TYPE_ID_1D_ARRAY_BIT, std::vector<float>);
// TODO(syoyo): Implement more types...
#undef TYPECAST_BASETYPE
#endif
#if 0
//
// Type checker
//
@@ -1195,19 +1216,21 @@ struct is_type {
return TypeTrait<T>::type_id == v.type_id();
}
};
#endif
struct AttribMap {
std::map<std::string, any_value> attribs;
std::map<std::string, Value> attribs;
};
} // namespace value
} // namespace tinyusdz
} // namespace value
} // namespace tinyusdz
namespace tinyusdz {
namespace value {
static_assert(sizeof(quath) == 8, "sizeof(quath) must be 8");
static_assert(sizeof(quatf) == 16, "sizeof(quatf) must be 16");
static_assert(sizeof(quatd) == 32, "sizeof(quatd) must be 32");
static_assert(sizeof(half) == 2, "sizeof(half) must be 2");
static_assert(sizeof(half2) == 4, "sizeof(half2) must be 4");
static_assert(sizeof(half3) == 6, "sizeof(half3) must be 6");
@@ -1216,5 +1239,5 @@ static_assert(sizeof(float3) == 12, "sizeof(float3) must be 12");
static_assert(sizeof(color3f) == 12, "sizeof(color3f) must be 12");
static_assert(sizeof(color4f) == 16, "sizeof(color4f) must be 16");
} // namespace value
} // namespace tinyusdz
} // namespace value
} // namespace tinyusdz

View File

@@ -53,6 +53,7 @@ static bool ReconstructVertrices(const any_value &v, Mesh &mesh) {
void primvar_test(void) {
#if 0
{
any_value f = 1.2f;
TEST_CHECK(is_float(f));
@@ -74,6 +75,7 @@ void primvar_test(void) {
auto c = typecast<TYPE_ID_FLOAT | TYPE_ID_1D_ARRAY_BIT>::to(f);
std::cout << "c = " << c << "\n";
}
#endif
#if 0
{