<commit_before>#pragma once

#if !(_MSC_VER && !__INTEL_COMPILER)

#include <boost/bimap.hpp>
#include <boost/bimap/list_of.hpp>
#include <boost/bimap/set_of.hpp>
#include <boost/function.hpp>

#include "config.hpp"
#include "misc.hpp"
#include "numerics.hpp"
#include "cycletimer.hpp"

#if ENABLE_MULTITHREADING && ENABLE_OPENMP
#include <omp.h>
#endif

/// This header file implements multiple versions of an LRU cache.
///
/// SingleCache is the basic cache implementation, where all threads
/// are expected to access a single cache.
///
/// MultiCache implements 1 SingleCache for each thread, avoiding the
/// need for locking in each of the Caches.
///
/// MultiRingCache implements 1 SingleCache for each thread, however, each
/// cache is responsible for a sequential set of key values (instead of
/// each cache being responsible for a thread) arranged in a ring.
/// However, each SingleCache must be lock protected, since multiple threads
/// may map their keys to the same cache.
///
/// MultiRingPriorityCache implements the same thing as the MultiRingCache
/// except there are no locks within each SingleCache.  Instead the lock
/// exists within the ring such that a thread will try to choose the cache
/// that has been assigned to the key value if possible, otherwise it will just
/// query the next non locked cache.

/// Defines an LRU cache.
/// If B is true, this class is thread safe, else it is not thread safe.
/// K is the key type, V is the value type, and SET is the SET storage type, ex. boost::bimap::set_of
template <bool B, typename K, typename V>
class SingleCache {
 public:
  typedef boost::bimaps::bimap<boost::bimaps::set_of<K>, boost::bimaps::list_of<V> > container_type;

  // Constuctor specifies the cached function and
  // the maximum number of records to be stored.
  SingleCache(const boost::function<V(const K&)>& f, size_t c) : _fn(f), _capacity(c) {
    cache_hits = 0;
    cache_misses = 0;
  }

  // Non locking version
  template<typename U = V> typename std::enable_if<!B, U>::type operator()(const K& k) {
    SCOPED_TIMER
    double startlookup = CycleTimer::currentSeconds();
    const typename container_type::left_iterator it = _container.left.find(k);
    if (it == _container.left.end()) {
      V v = _fn(k);
      cache_misses++;
      insert(k,v);
      return v;
    } else {
      cache_hits++;
      _container.right.relocate(_container.right.end(), _container.project_right(it));
      return it->second;
    }
    _lookup_time_total += CycleTimer::currentSeconds() - startlookup;
  }

  template<typename U = std::vector<V> > typename std::enable_if<!B, U>::type operator()(const std::vector<K> &k) {
    U u(k.size());
    for(size_t i=0; i<k.size(); i++) {
      u[i] = (*this)(k);
    }
    return u;
  }

  // Locking version
  template<typename U = V> typename std::enable_if<B, U>::type operator()(const K& k) {
    SCOPED_TIMER
    V v;
    #pragma omp critical
    {
      const typename container_type::left_iterator it = _container.left.find(k);
      if (it == _container.left.end()) {
        v = _fn(k);
        cache_misses++;
        insert(k,v);
      } else {
        cache_hits++;
        _container.right.relocate(_container.right.end(), _container.project_right(it));
        v = it->second;
      }
    }
    return v;
  }

  template<typename U = std::vector<V> > typename std::enable_if<B, U>::type operator()(const std::vector<K> &k) {
    U u(k.size());
    for(size_t i=0; i<k.size(); i++) {
      u[i] = (*this)(k);
    }
    return u;
  }

  uint64_t hits()             const { return cache_hits; }
  uint64_t misses()           const { return cache_misses; }
  uint64_t capacity()         const { return _capacity; }
  uint64_t num_lookups()      const { return cache_misses + cache_hits; }
  double total_lookup_time()  const { return _lookup_time_total; }

 private:
  void insert(const K& k, const V& v) {
    if (_container.size() == _capacity) {
      _container.right.erase(_container.right.begin());
    }
    _container.insert(typename container_type::value_type(k,v));
  }

  const boost::function<V(const K&)> _fn;
  const size_t _capacity;
  container_type _container;

  uint64_t cache_hits, cache_misses;

  double _lookup_time_total;
};

#if ENABLE_MULTITHREADING && ENABLE_OPENMP
/// Implements a SingleCache for each OpenMP thread.
template <typename K, typename V>
class MultiCache {
 public:

  MultiCache(const boost::function<V(const K&)>& f, size_t c) : _fn(f), _capacity(c) {
    for(int i=0; i < omp_get_max_threads(); i++){
      _caches.push_back(SingleCache<false, K, V>(f, c / omp_get_max_threads()));
    }
  }

  // Obtain value of the cached function for k
  V operator()(const K& k) {
    SCOPED_TIMER
    return _caches[omp_get_thread_num()](k);
  }

  uint64_t hits() const {
    int total_hits = 0;
    for(size_t i=0; i<_caches.size(); i++) total_hits += _caches[i].hits();
    return total_hits;
  }
  uint64_t misses() const {
    int total_misses = 0;
    for(size_t i=0; i<_caches.size(); i++) total_misses += _caches[i].misses();
    return total_misses;
  }

  uint64_t capacity() const { return _capacity; }

 private:

  std::vector<SingleCache<false, K, V>> _caches;
  const boost::function<V(const K&)> _fn;
  const size_t _capacity;
};

/// Implements a SingleCache for each OpenMP thread, where each cache is responsible
/// for sequential ranges of items, this requires that K has modulo operator % and a relation operator <.
/// This is essentially a bunch of caches arranged in a ring topology.
template <typename K, typename V>
class MultiRingCache {
 public:

  MultiRingCache(const boost::function<V(const K&)>& f, size_t c) : _fn(f), _capacity(c), _single_capacity(c / omp_get_max_threads()) {
    for(int i=0; i < omp_get_max_threads(); i++){
      _caches.push_back(SingleCache<true, K, V>(f, _single_capacity));
    }
  }

  // Obtain value of the cached function for k
  V operator()(const K& k) {
    SCOPED_TIMER
    return _caches[(size_t)(k / _single_capacity) % omp_get_max_threads()](k);
  }

  uint64_t hits() const {
    int total_hits = 0;
    for(size_t i=0; i<_caches.size(); i++) total_hits += _caches[i].hits();
    return total_hits;
  }
  uint64_t misses() const {
    int total_misses = 0;
    for(size_t i=0; i<_caches.size(); i++) total_misses += _caches[i].misses();
    return total_misses;
  }

  uint64_t num_lookups() const {
    int total_lookups = 0;
    for(size_t i=0; i<_caches.size(); i++) total_lookups += _caches[i].num_lookups();
    return total_lookups;
  }

  double total_lookup_time()const {
    double lookup_time = 0;
    for(size_t i=0; i<_caches.size(); i++) lookup_time += _caches[i].total_lookup_time();
    return lookup_time;
  }

  uint64_t capacity() const { return _capacity; }

 private:
  std::vector<SingleCache<true, K, V>> _caches;
  const boost::function<V(const K&)> _fn;
  const size_t _capacity, _single_capacity;
};

/// Implements a SingleCache for each OpenMP thread, where each cache is responsible
/// for sequential ranges of items, this requires that K has modulo operator % and a relation operator <.
/// This is essentially a bunch of caches arranged in a ring topology.
template <typename K, typename V>
class MultiRingPriorityCache {
 public:

  MultiRingPriorityCache(const boost::function<V(const K&)>& f, size_t c) : _fn(f), _capacity(c),
    _single_capacity(c / omp_get_max_threads()) {

    _locks.resize(omp_get_max_threads());
    for(int i=0; i < omp_get_max_threads(); i++){
      _caches.push_back(SingleCache<false, K, V>(f, _single_capacity));
      omp_init_lock(&_locks[i]);
    }
  }

  // Obtain value of the cached function for k
  V operator()(const K& k) {
    SCOPED_TIMER
    // double startlookup = CycleTimer::currentSeconds();
    for(size_t i = 0; i<omp_get_max_threads(); i++) {
      size_t cache_idx = ((size_t)(k / _single_capacity)+i) % omp_get_max_threads();
      int lock_acquired = omp_test_lock(&_locks[cache_idx]);
      if(lock_acquired) {
        V v = _caches[cache_idx](k);
        omp_unset_lock(&_locks[cache_idx]);
        return v;
      }
    }

    size_t cache_idx = ((size_t)(k / _single_capacity)) % omp_get_max_threads();
    omp_set_lock(&_locks[cache_idx]);
    V v = _caches[cache_idx](k);
    omp_unset_lock(&_locks[cache_idx]);

    // #pragma omp critical
    // _lookup_time_total += CycleTimer::currentSeconds() - startlookup;
    return v;
  }

  uint64_t hits() const {
    int total_hits = 0;
    for(size_t i=0; i<_caches.size(); i++) total_hits += _caches[i].hits();
    return total_hits;
  }

  uint64_t misses() const {
    int total_misses = 0;
    for(size_t i=0; i<_caches.size(); i++) total_misses += _caches[i].misses();
    return total_misses;
  }

  uint64_t num_lookups() const {
    int total_lookups = 0;
    for(size_t i=0; i<_caches.size(); i++) total_lookups += _caches[i].num_lookups();
    return total_lookups;
  }

  double total_lookup_time()const {
    return _lookup_time_total;
  }

  uint64_t capacity() const { return _capacity; }

 private:

  std::vector<SingleCache<false, K, V> > _caches;
  std::vector<omp_lock_t> _locks;

  const boost::function<V(const K&)> _fn;
  const size_t _capacity, _single_capacity;
  double _lookup_time_total;
};

template < typename K, typename V>
std::ostream& operator<< (std::ostream &out, const MultiCache<K, V> &c) {
  out << "Cache [ capacity: " << c.capacity() << ", hits: " << c.hits()
    << ", misses: " << c.misses() << ", hit rate: " << c.hits() / (float)(c.hits() + c.misses())
    << " ]";
  return out;
}

template < typename K, typename V>
std::ostream& operator<< (std::ostream &out, const MultiRingCache<K, V> &c) {
  out << "Cache [ capacity: " << c.capacity() << ", hits: " << c.hits()
    << ", misses: " << c.misses() << ", hit rate: " << c.hits() / (float)(c.hits() + c.misses())
    << " ]";
  return out;
}

template < typename K, typename V>
std::ostream& operator<< (std::ostream &out, const MultiRingPriorityCache<K, V> &c) {
  out << "Cache [ capacity: " << c.capacity() << ", hits: " << c.hits()
    << ", misses: " << c.misses() << ", hit rate: " << c.hits() / (float)(c.hits() + c.misses())
    << " ]";
  return out;
}

typedef MultiRingPriorityCache<uint64_t, numerics::sparse_vector_t> bow_ring_priority_cache_t;
typedef MultiRingCache<uint64_t, numerics::sparse_vector_t> bow_ring_cache_t;
typedef MultiCache<uint64_t, numerics::sparse_vector_t> bow_multi_cache_t;

typedef MultiRingPriorityCache<uint64_t,  std::vector<float>> vec_ring_priority_cache_t;
typedef MultiRingCache<uint64_t,  std::vector<float>> vec_ring_cache_t;
typedef MultiCache<uint64_t, std::vector<float>> vec_multi_cache_t;

#endif

template < bool B, typename K, typename V>
std::ostream& operator<< (std::ostream &out, const SingleCache<B, K, V> &c) {
  out << "Cache [ capacity: " << c.capacity() << ", hits: " << c.hits()
    << ", misses: " << c.misses() << ", hit rate: " << c.hits() / (float)(c.hits() + c.misses())
    << " ]";
  return out;
}

typedef SingleCache<true, uint64_t, numerics::sparse_vector_t> bow_single_cache_t;
typedef SingleCache<true, uint64_t, std::vector<float>> vec_single_cache_t;

#endif
<commit_msg>caches are now copyable<commit_after>#pragma once

#if !(_MSC_VER && !__INTEL_COMPILER)

#include <boost/bimap.hpp>
#include <boost/bimap/list_of.hpp>
#include <boost/bimap/set_of.hpp>
#include <boost/function.hpp>

#include "config.hpp"
#include "misc.hpp"
#include "numerics.hpp"
#include "cycletimer.hpp"

#if ENABLE_MULTITHREADING && ENABLE_OPENMP
#include <omp.h>
#endif

/// This header file implements multiple versions of an LRU cache.
///
/// SingleCache is the basic cache implementation, where all threads
/// are expected to access a single cache.
///
/// MultiCache implements 1 SingleCache for each thread, avoiding the
/// need for locking in each of the Caches.
///
/// MultiRingCache implements 1 SingleCache for each thread, however, each
/// cache is responsible for a sequential set of key values (instead of
/// each cache being responsible for a thread) arranged in a ring.
/// However, each SingleCache must be lock protected, since multiple threads
/// may map their keys to the same cache.
///
/// MultiRingPriorityCache implements the same thing as the MultiRingCache
/// except there are no locks within each SingleCache.  Instead the lock
/// exists within the ring such that a thread will try to choose the cache
/// that has been assigned to the key value if possible, otherwise it will just
/// query the next non locked cache.

/// Defines an LRU cache.
/// If B is true, this class is thread safe, else it is not thread safe.
/// K is the key type, V is the value type, and SET is the SET storage type, ex. boost::bimap::set_of
template <bool B, typename K, typename V>
class SingleCache {
 public:
  typedef boost::bimaps::bimap<boost::bimaps::set_of<K>, boost::bimaps::list_of<V> > container_type;

  // Constuctor specifies the cached function and
  // the maximum number of records to be stored.
  SingleCache(const boost::function<V(const K&)>& f, size_t c) : _fn(f), _capacity(c) {
    cache_hits = 0;
    cache_misses = 0;
  }

  // Non locking version
  template<typename U = V> typename std::enable_if<!B, U>::type operator()(const K& k) {
    SCOPED_TIMER
    double startlookup = CycleTimer::currentSeconds();
    const typename container_type::left_iterator it = _container.left.find(k);
    if (it == _container.left.end()) {
      V v = _fn(k);
      cache_misses++;
      insert(k,v);
      return v;
    } else {
      cache_hits++;
      _container.right.relocate(_container.right.end(), _container.project_right(it));
      return it->second;
    }
    _lookup_time_total += CycleTimer::currentSeconds() - startlookup;
  }

  template<typename U = std::vector<V> > typename std::enable_if<!B, U>::type operator()(const std::vector<K> &k) {
    U u(k.size());
    for(size_t i=0; i<k.size(); i++) {
      u[i] = (*this)(k);
    }
    return u;
  }

  // Locking version
  template<typename U = V> typename std::enable_if<B, U>::type operator()(const K& k) {
    SCOPED_TIMER
    V v;
    #pragma omp critical
    {
      const typename container_type::left_iterator it = _container.left.find(k);
      if (it == _container.left.end()) {
        v = _fn(k);
        cache_misses++;
        insert(k,v);
      } else {
        cache_hits++;
        _container.right.relocate(_container.right.end(), _container.project_right(it));
        v = it->second;
      }
    }
    return v;
  }

  template<typename U = std::vector<V> > typename std::enable_if<B, U>::type operator()(const std::vector<K> &k) {
    U u(k.size());
    for(size_t i=0; i<k.size(); i++) {
      u[i] = (*this)(k);
    }
    return u;
  }

  uint64_t hits()             const { return cache_hits; }
  uint64_t misses()           const { return cache_misses; }
  uint64_t capacity()         const { return _capacity; }
  uint64_t num_lookups()      const { return cache_misses + cache_hits; }
  double total_lookup_time()  const { return _lookup_time_total; }

 private:
  void insert(const K& k, const V& v) {
    if (_container.size() == _capacity) {
      _container.right.erase(_container.right.begin());
    }
    _container.insert(typename container_type::value_type(k,v));
  }

  const boost::function<V(const K&)> _fn;
  const size_t _capacity;
  container_type _container;

  uint64_t cache_hits, cache_misses;

  double _lookup_time_total;
};

#if ENABLE_MULTITHREADING && ENABLE_OPENMP
/// Implements a SingleCache for each OpenMP thread.
template <typename K, typename V>
class MultiCache {
 public:

  MultiCache(const boost::function<V(const K&)>& f, size_t c) : _fn(f), _capacity(c) {
    for(int i=0; i < omp_get_max_threads(); i++){
      _caches.push_back(PTR_LIB::make_shared<SingleCache<false, K, V>>(f, c / omp_get_max_threads()));
    }
  }

  // Obtain value of the cached function for k
  V operator()(const K& k) {
    SCOPED_TIMER
    return (*_caches[omp_get_thread_num()])(k);
  }

  uint64_t hits() const {
    int total_hits = 0;
    for(size_t i=0; i<_caches.size(); i++) total_hits += _caches[i]->hits();
    return total_hits;
  }
  uint64_t misses() const {
    int total_misses = 0;
    for(size_t i=0; i<_caches.size(); i++) total_misses += _caches[i]->misses();
    return total_misses;
  }

  uint64_t capacity() const { return _capacity; }

 private:

  std::vector< PTR_LIB::shared_ptr<SingleCache<false, K, V> > > _caches;
  const boost::function<V(const K&)> _fn;
  const size_t _capacity;
};

/// Implements a SingleCache for each OpenMP thread, where each cache is responsible
/// for sequential ranges of items, this requires that K has modulo operator % and a relation operator <.
/// This is essentially a bunch of caches arranged in a ring topology.
template <typename K, typename V>
class MultiRingCache {
 public:

  MultiRingCache(const boost::function<V(const K&)>& f, size_t c) : _fn(f), _capacity(c), _single_capacity(c / omp_get_max_threads()) {
    for(int i=0; i < omp_get_max_threads(); i++){
      _caches.push_back(PTR_LIB::make_shared<SingleCache<false, K, V>>(f, _single_capacity));
    }
  }

  // Obtain value of the cached function for k
  V operator()(const K& k) {
    SCOPED_TIMER
    return (*_caches[(size_t)(k / _single_capacity) % omp_get_max_threads()])(k);
  }

  uint64_t hits() const {
    int total_hits = 0;
    for(size_t i=0; i<_caches.size(); i++) total_hits += _caches[i]->hits();
    return total_hits;
  }
  uint64_t misses() const {
    int total_misses = 0;
    for(size_t i=0; i<_caches.size(); i++) total_misses += _caches[i]->misses();
    return total_misses;
  }

  uint64_t num_lookups() const {
    int total_lookups = 0;
    for(size_t i=0; i<_caches.size(); i++) total_lookups += _caches[i]->num_lookups();
    return total_lookups;
  }

  double total_lookup_time()const {
    double lookup_time = 0;
    for(size_t i=0; i<_caches.size(); i++) lookup_time += _caches[i]->total_lookup_time();
    return lookup_time;
  }

  uint64_t capacity() const { return _capacity; }

 private:
  std::vector< PTR_LIB::shared_ptr<SingleCache<false, K, V> > > _caches;
  const boost::function<V(const K&)> _fn;
  const size_t _capacity, _single_capacity;
};

/// Implements a SingleCache for each OpenMP thread, where each cache is responsible
/// for sequential ranges of items, this requires that K has modulo operator % and a relation operator <.
/// This is essentially a bunch of caches arranged in a ring topology.
template <typename K, typename V>
class MultiRingPriorityCache {
 public:

  MultiRingPriorityCache(const boost::function<V(const K&)>& f, size_t c) : _fn(f), _capacity(c),
    _single_capacity(c / omp_get_max_threads()) {

    _locks.resize(omp_get_max_threads());
    for(int i=0; i < omp_get_max_threads(); i++){
      _caches.push_back(PTR_LIB::make_shared<SingleCache<false, K, V>>(f, _single_capacity));
      omp_init_lock(&_locks[i]);
    }
  }

  // Obtain value of the cached function for k
  V operator()(const K& k) {
    SCOPED_TIMER
    // double startlookup = CycleTimer::currentSeconds();
    for(size_t i = 0; i<omp_get_max_threads(); i++) {
      size_t cache_idx = ((size_t)(k / _single_capacity)+i) % omp_get_max_threads();
      int lock_acquired = omp_test_lock(&_locks[cache_idx]);
      if(lock_acquired) {
        V v = (*_caches[cache_idx])(k);
        omp_unset_lock(&_locks[cache_idx]);
        return v;
      }
    }

    size_t cache_idx = ((size_t)(k / _single_capacity)) % omp_get_max_threads();
    omp_set_lock(&_locks[cache_idx]);
    V v = (*_caches[cache_idx])(k);
    omp_unset_lock(&_locks[cache_idx]);

    // #pragma omp critical
    // _lookup_time_total += CycleTimer::currentSeconds() - startlookup;
    return v;
  }

  uint64_t hits() const {
    int total_hits = 0;
    for(size_t i=0; i<_caches.size(); i++) total_hits += _caches[i]->hits();
    return total_hits;
  }

  uint64_t misses() const {
    int total_misses = 0;
    for(size_t i=0; i<_caches.size(); i++) total_misses += _caches[i]->misses();
    return total_misses;
  }

  uint64_t num_lookups() const {
    int total_lookups = 0;
    for(size_t i=0; i<_caches.size(); i++) total_lookups += _caches[i]->num_lookups();
    return total_lookups;
  }

  double total_lookup_time()const {
    return _lookup_time_total;
  }

  uint64_t capacity() const { return _capacity; }

 private:

  std::vector< PTR_LIB::shared_ptr<SingleCache<false, K, V> > > _caches;
  std::vector<omp_lock_t> _locks;

  const boost::function<V(const K&)> _fn;
  const size_t _capacity, _single_capacity;
  double _lookup_time_total;
};

template < typename K, typename V>
std::ostream& operator<< (std::ostream &out, const MultiCache<K, V> &c) {
  out << "Cache [ capacity: " << c.capacity() << ", hits: " << c.hits()
    << ", misses: " << c.misses() << ", hit rate: " << c.hits() / (float)(c.hits() + c.misses())
    << " ]";
  return out;
}

template < typename K, typename V>
std::ostream& operator<< (std::ostream &out, const MultiRingCache<K, V> &c) {
  out << "Cache [ capacity: " << c.capacity() << ", hits: " << c.hits()
    << ", misses: " << c.misses() << ", hit rate: " << c.hits() / (float)(c.hits() + c.misses())
    << " ]";
  return out;
}

template < typename K, typename V>
std::ostream& operator<< (std::ostream &out, const MultiRingPriorityCache<K, V> &c) {
  out << "Cache [ capacity: " << c.capacity() << ", hits: " << c.hits()
    << ", misses: " << c.misses() << ", hit rate: " << c.hits() / (float)(c.hits() + c.misses())
    << " ]";
  return out;
}

typedef MultiRingPriorityCache<uint64_t, numerics::sparse_vector_t> bow_ring_priority_cache_t;
typedef MultiRingCache<uint64_t, numerics::sparse_vector_t> bow_ring_cache_t;
typedef MultiCache<uint64_t, numerics::sparse_vector_t> bow_multi_cache_t;

typedef MultiRingPriorityCache<uint64_t,  std::vector<float>> vec_ring_priority_cache_t;
typedef MultiRingCache<uint64_t,  std::vector<float>> vec_ring_cache_t;
typedef MultiCache<uint64_t, std::vector<float>> vec_multi_cache_t;

#endif

template < bool B, typename K, typename V>
std::ostream& operator<< (std::ostream &out, const SingleCache<B, K, V> &c) {
  out << "Cache [ capacity: " << c.capacity() << ", hits: " << c.hits()
    << ", misses: " << c.misses() << ", hit rate: " << c.hits() / (float)(c.hits() + c.misses())
    << " ]";
  return out;
}

typedef SingleCache<true, uint64_t, numerics::sparse_vector_t> bow_single_cache_t;
typedef SingleCache<true, uint64_t, std::vector<float>> vec_single_cache_t;

#endif
<|endoftext|>


<commit_before>#include "CLIConfigParsing.h"
#include "mmcore/CoreInstance.h"
#include "mmcore/MegaMolGraph.h"

#include "mmcore/utility/log/Log.h"
#include "mmcore/utility/log/DefaultTarget.h"

#include "RuntimeConfig.h"
#include "FrameStatistics_Service.hpp"
#include "FrontendServiceCollection.hpp"
#include "GUI_Service.hpp"
#include "Lua_Service_Wrapper.hpp"
#include "OpenGL_GLFW_Service.hpp"
#include "Screenshot_Service.hpp"
#include "ProjectLoader_Service.hpp"

#include "mmcore/view/AbstractView_EventConsumption.h"

#include "mmcore/LuaAPI.h"

int main(const int argc, const char** argv) {

    bool lua_imperative_only = false; // allow mmFlush, mmList* and mmGetParam*
    megamol::core::LuaAPI lua_api(lua_imperative_only);

    auto config = megamol::frontend::handle_cli_and_config(argc, argv, lua_api);

    // setup log
    megamol::core::utility::log::Log::DefaultLog.SetLevel(config.log_level);
    megamol::core::utility::log::Log::DefaultLog.SetEchoLevel(config.echo_level);
    megamol::core::utility::log::Log::DefaultLog.SetLogFileName(config.log_file.data(), false);
    megamol::core::utility::log::Log::DefaultLog.SetOfflineMessageBufferSize(100);
    megamol::core::utility::log::Log::DefaultLog.SetMainTarget(
        std::make_shared<megamol::core::utility::log::DefaultTarget>(megamol::core::utility::log::Log::LEVEL_ALL));

    megamol::core::CoreInstance core;
    core.Initialise(false); // false makes core not start his own lua service (else we collide on default port)

    megamol::frontend::OpenGL_GLFW_Service gl_service;
    megamol::frontend::OpenGL_GLFW_Service::Config openglConfig;
    openglConfig.windowTitlePrefix = "MegaMol";
    openglConfig.versionMajor = 4;
    openglConfig.versionMinor = 5;
    openglConfig.enableKHRDebug = config.opengl_khr_debug;
    openglConfig.enableVsync = config.opengl_vsync;
    // pass window size and position
    if (!config.window_size.empty()) {
        assert(config.window_size.size() == 2);
        openglConfig.windowPlacement.size = true;
        openglConfig.windowPlacement.w = config.window_size[0];
        openglConfig.windowPlacement.h = config.window_size[1];
    }
    if (!config.window_position.empty()) {
        assert(config.window_position.size() == 2);
        openglConfig.windowPlacement.pos = true;
        openglConfig.windowPlacement.x = config.window_position[0];
        openglConfig.windowPlacement.y = config.window_position[1];
    }
    openglConfig.windowPlacement.mon = config.window_monitor;
    using megamol::frontend_resources::RuntimeConfig;
    openglConfig.windowPlacement.fullScreen = config.window_mode & RuntimeConfig::WindowMode::fullscreen;
    openglConfig.windowPlacement.noDec      = config.window_mode & RuntimeConfig::WindowMode::nodecoration;
    openglConfig.windowPlacement.topMost    = config.window_mode & RuntimeConfig::WindowMode::topmost;
    openglConfig.windowPlacement.noCursor   = config.window_mode & RuntimeConfig::WindowMode::nocursor;
    gl_service.setPriority(2);

    megamol::frontend::GUI_Service gui_service;
    megamol::frontend::GUI_Service::Config guiConfig;
    guiConfig.imgui_api = megamol::frontend::GUI_Service::ImGuiAPI::OPEN_GL;
    guiConfig.core_instance = &core;
    // priority must be higher than priority of gl_service (=1)
    // service callbacks get called in order of priority of the service.
    // postGraphRender() and close() are called in reverse order of priorities.
    gui_service.setPriority(23);

    megamol::frontend::Screenshot_Service screenshot_service;
    megamol::frontend::Screenshot_Service::Config screenshotConfig;
    screenshot_service.setPriority(30);

    megamol::frontend::FrameStatistics_Service framestatistics_service;
    megamol::frontend::FrameStatistics_Service::Config framestatisticsConfig;
    // needs to execute before gl_service at frame start, after gl service at frame end
    framestatistics_service.setPriority(1);

    megamol::frontend::Lua_Service_Wrapper lua_service_wrapper;
    megamol::frontend::Lua_Service_Wrapper::Config luaConfig;
    luaConfig.lua_api_ptr = &lua_api;
    luaConfig.host_address = config.lua_host_address;
    luaConfig.retry_socket_port = config.lua_host_port_retry;
    lua_service_wrapper.setPriority(0);

    megamol::frontend::ProjectLoader_Service projectloader_service;
    megamol::frontend::ProjectLoader_Service::Config projectloaderConfig;
    projectloader_service.setPriority(1);

    // clang-format off
    // the main loop is organized around services that can 'do something' in different parts of the main loop.
    // a service is something that implements the AbstractFrontendService interface from 'megamol\frontend_services\include'.
    // a central mechanism that allows services to communicate with each other and with graph modules are _resources_.
    // (see FrontendResource in 'megamol\frontend_resources\include').
    // services may provide resources to the system and they may request resources they need themselves for functioning.
    // think of a resource as a struct (or some type of your choice) that gets wrapped
    // by a helper structure and gets a name attached to it. the fronend makes sure (at least
    // attempts to) to hand each service the resources it requested, or else fail execution of megamol with an error message.
    // resource assignment is done by the name of the resource, so this is a very loose interface based on trust.
    // type safety of resources is ensured in the sense that extracting the wrong type from a FrontendResource will
    // lead to an unhandled bad type cast exception, leading to the shutdown of megamol.
    // clang-format on
    bool run_megamol = true;
    megamol::frontend::FrontendServiceCollection services;
    services.add(gl_service, &openglConfig);
    services.add(gui_service, &guiConfig);
    services.add(lua_service_wrapper, &luaConfig);
    services.add(screenshot_service, &screenshotConfig);
    services.add(framestatistics_service, &framestatisticsConfig);
    services.add(projectloader_service, &projectloaderConfig);

    // clang-format off
    // TODO: port cinematic as frontend service
    // TODO: FBO-centered rendering (View redesign)
    // => explicit FBOs!
    // => explicit camera / animation time / FBO resources/modules in graph?
    // => do or dont show GUI in screenshots, depending on ...
    // TODO: ZMQ context as frontend resource
    // TODO: eliminate the core instance:
    //  => extract module/call description manager into new factories; remove from core
    // TODO: main3000 raw hot loop performance vs. mmconsole performance
    // => has to collect graph serialization from graph, gui state from gui.
    // clang-format on

    const bool init_ok = services.init(); // runs init(config_ptr) on all services with provided config sructs

    if (!init_ok) {
        std::cout << "ERROR: some frontend service could not be initialized successfully. abort. " << std::endl;
        services.close();
        return 1;
    }

    const megamol::core::factories::ModuleDescriptionManager& moduleProvider = core.GetModuleDescriptionManager();
    const megamol::core::factories::CallDescriptionManager& callProvider = core.GetCallDescriptionManager();

    megamol::core::MegaMolGraph graph(core, moduleProvider, callProvider);

    // graph is also a resource that may be accessed by services
    services.getProvidedResources().push_back({"MegaMolGraph", graph});
    services.getProvidedResources().push_back({"RuntimeConfig", config});

    // proof of concept: a resource that returns a list of names of available resources
    // used by Lua Wrapper and LuaAPI to return list of available resources via remoteconsole
    const std::function<std::vector<std::string>()> resource_lister = [&]() -> std::vector<std::string> {
        std::vector<std::string> resources;
        for (auto& resource : services.getProvidedResources()) {
            resources.push_back(resource.getIdentifier());
        }
        resources.push_back("FrontendResourcesList");
        return resources;
    };
    services.getProvidedResources().push_back({"FrontendResourcesList", resource_lister});

    // distribute registered resources among registered services.
    const bool resources_ok = services.assignRequestedResources();
    // for each service we call their resource callbacks here:
    //    std::vector<FrontendResource>& getProvidedResources()
    //    std::vector<std::string> getRequestedResourceNames()
    //    void setRequestedResources(std::vector<FrontendResource>& resources)
    if (!resources_ok) {
        std::cout << "ERROR: frontend could not assign requested service resources. abort. " << std::endl;
        run_megamol = false;
    }

    auto frontend_resources = services.getProvidedResources();
    graph.AddModuleDependencies(frontend_resources);

    uint32_t frameID = 0;
    const auto render_next_frame = [&]() -> bool {
        // set global Frame Counter
        core.SetFrameID(frameID++);

        // services: receive inputs (GLFW poll events [keyboard, mouse, window], network, lua)
        services.updateProvidedResources();

        // aka simulation step
        // services: digest new inputs via ModuleResources (GUI digest user inputs, lua digest inputs, network ?)
        // e.g. graph updates, module and call creation via lua and GUI happen here
        services.digestChangedRequestedResources();

        // services tell us wheter we should shut down megamol
        if (services.shouldShutdown())
            return false;

        // actual rendering
        {
            services.preGraphRender(); // e.g. start frame timer, clear render buffers

            graph.RenderNextFrame(); // executes graph views, those digest input events like keyboard/mouse, then render

            services.postGraphRender(); // render GUI, glfw swap buffers, stop frame timer
        }

        services.resetProvidedResources(); // clear buffers holding glfw keyboard+mouse input

        return true;
    };

    // lua can issue rendering of frames
    lua_api.setFlushCallback(render_next_frame);

    // load project files via lua
    for (auto& file : config.project_files) {
        if (!projectloader_service.load_file(file)) {
            std::cout << "Project file \"" << file << "\" did not execute correctly"<< std::endl;
            run_megamol = false;

            // if interactive, continue to run MegaMol
            if (config.interactive) {
                std::cout << "Interactive mode: start MegaMol anyway"<< std::endl;
                run_megamol = true;
            }
        }
    }

    while (run_megamol) {
        run_megamol = render_next_frame();
    }

    graph.Clear();

    // close glfw context, network connections, other system resources
    services.close();

    return 0;
}

<commit_msg>main: fix writing to log file<commit_after>#include "CLIConfigParsing.h"
#include "mmcore/CoreInstance.h"
#include "mmcore/MegaMolGraph.h"

#include "mmcore/utility/log/Log.h"
#include "mmcore/utility/log/DefaultTarget.h"

#include "RuntimeConfig.h"
#include "FrameStatistics_Service.hpp"
#include "FrontendServiceCollection.hpp"
#include "GUI_Service.hpp"
#include "Lua_Service_Wrapper.hpp"
#include "OpenGL_GLFW_Service.hpp"
#include "Screenshot_Service.hpp"
#include "ProjectLoader_Service.hpp"

#include "mmcore/view/AbstractView_EventConsumption.h"

#include "mmcore/LuaAPI.h"

int main(const int argc, const char** argv) {

    bool lua_imperative_only = false; // allow mmFlush, mmList* and mmGetParam*
    megamol::core::LuaAPI lua_api(lua_imperative_only);

    auto config = megamol::frontend::handle_cli_and_config(argc, argv, lua_api);

    // setup log
    megamol::core::utility::log::Log::DefaultLog.SetLevel(config.log_level);
    megamol::core::utility::log::Log::DefaultLog.SetEchoLevel(config.echo_level);
    megamol::core::utility::log::Log::DefaultLog.SetOfflineMessageBufferSize(100);
    megamol::core::utility::log::Log::DefaultLog.SetMainTarget(
        std::make_shared<megamol::core::utility::log::DefaultTarget>());
    megamol::core::utility::log::Log::DefaultLog.SetLogFileName(config.log_file.data(), false);

    megamol::core::CoreInstance core;
    core.Initialise(false); // false makes core not start his own lua service (else we collide on default port)

    megamol::frontend::OpenGL_GLFW_Service gl_service;
    megamol::frontend::OpenGL_GLFW_Service::Config openglConfig;
    openglConfig.windowTitlePrefix = "MegaMol";
    openglConfig.versionMajor = 4;
    openglConfig.versionMinor = 5;
    openglConfig.enableKHRDebug = config.opengl_khr_debug;
    openglConfig.enableVsync = config.opengl_vsync;
    // pass window size and position
    if (!config.window_size.empty()) {
        assert(config.window_size.size() == 2);
        openglConfig.windowPlacement.size = true;
        openglConfig.windowPlacement.w = config.window_size[0];
        openglConfig.windowPlacement.h = config.window_size[1];
    }
    if (!config.window_position.empty()) {
        assert(config.window_position.size() == 2);
        openglConfig.windowPlacement.pos = true;
        openglConfig.windowPlacement.x = config.window_position[0];
        openglConfig.windowPlacement.y = config.window_position[1];
    }
    openglConfig.windowPlacement.mon = config.window_monitor;
    using megamol::frontend_resources::RuntimeConfig;
    openglConfig.windowPlacement.fullScreen = config.window_mode & RuntimeConfig::WindowMode::fullscreen;
    openglConfig.windowPlacement.noDec      = config.window_mode & RuntimeConfig::WindowMode::nodecoration;
    openglConfig.windowPlacement.topMost    = config.window_mode & RuntimeConfig::WindowMode::topmost;
    openglConfig.windowPlacement.noCursor   = config.window_mode & RuntimeConfig::WindowMode::nocursor;
    gl_service.setPriority(2);

    megamol::frontend::GUI_Service gui_service;
    megamol::frontend::GUI_Service::Config guiConfig;
    guiConfig.imgui_api = megamol::frontend::GUI_Service::ImGuiAPI::OPEN_GL;
    guiConfig.core_instance = &core;
    // priority must be higher than priority of gl_service (=1)
    // service callbacks get called in order of priority of the service.
    // postGraphRender() and close() are called in reverse order of priorities.
    gui_service.setPriority(23);

    megamol::frontend::Screenshot_Service screenshot_service;
    megamol::frontend::Screenshot_Service::Config screenshotConfig;
    screenshot_service.setPriority(30);

    megamol::frontend::FrameStatistics_Service framestatistics_service;
    megamol::frontend::FrameStatistics_Service::Config framestatisticsConfig;
    // needs to execute before gl_service at frame start, after gl service at frame end
    framestatistics_service.setPriority(1);

    megamol::frontend::Lua_Service_Wrapper lua_service_wrapper;
    megamol::frontend::Lua_Service_Wrapper::Config luaConfig;
    luaConfig.lua_api_ptr = &lua_api;
    luaConfig.host_address = config.lua_host_address;
    luaConfig.retry_socket_port = config.lua_host_port_retry;
    lua_service_wrapper.setPriority(0);

    megamol::frontend::ProjectLoader_Service projectloader_service;
    megamol::frontend::ProjectLoader_Service::Config projectloaderConfig;
    projectloader_service.setPriority(1);

    // clang-format off
    // the main loop is organized around services that can 'do something' in different parts of the main loop.
    // a service is something that implements the AbstractFrontendService interface from 'megamol\frontend_services\include'.
    // a central mechanism that allows services to communicate with each other and with graph modules are _resources_.
    // (see FrontendResource in 'megamol\frontend_resources\include').
    // services may provide resources to the system and they may request resources they need themselves for functioning.
    // think of a resource as a struct (or some type of your choice) that gets wrapped
    // by a helper structure and gets a name attached to it. the fronend makes sure (at least
    // attempts to) to hand each service the resources it requested, or else fail execution of megamol with an error message.
    // resource assignment is done by the name of the resource, so this is a very loose interface based on trust.
    // type safety of resources is ensured in the sense that extracting the wrong type from a FrontendResource will
    // lead to an unhandled bad type cast exception, leading to the shutdown of megamol.
    // clang-format on
    bool run_megamol = true;
    megamol::frontend::FrontendServiceCollection services;
    services.add(gl_service, &openglConfig);
    services.add(gui_service, &guiConfig);
    services.add(lua_service_wrapper, &luaConfig);
    services.add(screenshot_service, &screenshotConfig);
    services.add(framestatistics_service, &framestatisticsConfig);
    services.add(projectloader_service, &projectloaderConfig);

    // clang-format off
    // TODO: port cinematic as frontend service
    // TODO: FBO-centered rendering (View redesign)
    // => explicit FBOs!
    // => explicit camera / animation time / FBO resources/modules in graph?
    // => do or dont show GUI in screenshots, depending on ...
    // TODO: ZMQ context as frontend resource
    // TODO: eliminate the core instance:
    //  => extract module/call description manager into new factories; remove from core
    // TODO: main3000 raw hot loop performance vs. mmconsole performance
    // => has to collect graph serialization from graph, gui state from gui.
    // clang-format on

    const bool init_ok = services.init(); // runs init(config_ptr) on all services with provided config sructs

    if (!init_ok) {
        std::cout << "ERROR: some frontend service could not be initialized successfully. abort. " << std::endl;
        services.close();
        return 1;
    }

    const megamol::core::factories::ModuleDescriptionManager& moduleProvider = core.GetModuleDescriptionManager();
    const megamol::core::factories::CallDescriptionManager& callProvider = core.GetCallDescriptionManager();

    megamol::core::MegaMolGraph graph(core, moduleProvider, callProvider);

    // graph is also a resource that may be accessed by services
    services.getProvidedResources().push_back({"MegaMolGraph", graph});
    services.getProvidedResources().push_back({"RuntimeConfig", config});

    // proof of concept: a resource that returns a list of names of available resources
    // used by Lua Wrapper and LuaAPI to return list of available resources via remoteconsole
    const std::function<std::vector<std::string>()> resource_lister = [&]() -> std::vector<std::string> {
        std::vector<std::string> resources;
        for (auto& resource : services.getProvidedResources()) {
            resources.push_back(resource.getIdentifier());
        }
        resources.push_back("FrontendResourcesList");
        return resources;
    };
    services.getProvidedResources().push_back({"FrontendResourcesList", resource_lister});

    // distribute registered resources among registered services.
    const bool resources_ok = services.assignRequestedResources();
    // for each service we call their resource callbacks here:
    //    std::vector<FrontendResource>& getProvidedResources()
    //    std::vector<std::string> getRequestedResourceNames()
    //    void setRequestedResources(std::vector<FrontendResource>& resources)
    if (!resources_ok) {
        std::cout << "ERROR: frontend could not assign requested service resources. abort. " << std::endl;
        run_megamol = false;
    }

    auto frontend_resources = services.getProvidedResources();
    graph.AddModuleDependencies(frontend_resources);

    uint32_t frameID = 0;
    const auto render_next_frame = [&]() -> bool {
        // set global Frame Counter
        core.SetFrameID(frameID++);

        // services: receive inputs (GLFW poll events [keyboard, mouse, window], network, lua)
        services.updateProvidedResources();

        // aka simulation step
        // services: digest new inputs via ModuleResources (GUI digest user inputs, lua digest inputs, network ?)
        // e.g. graph updates, module and call creation via lua and GUI happen here
        services.digestChangedRequestedResources();

        // services tell us wheter we should shut down megamol
        if (services.shouldShutdown())
            return false;

        // actual rendering
        {
            services.preGraphRender(); // e.g. start frame timer, clear render buffers

            graph.RenderNextFrame(); // executes graph views, those digest input events like keyboard/mouse, then render

            services.postGraphRender(); // render GUI, glfw swap buffers, stop frame timer
        }

        services.resetProvidedResources(); // clear buffers holding glfw keyboard+mouse input

        return true;
    };

    // lua can issue rendering of frames
    lua_api.setFlushCallback(render_next_frame);

    // load project files via lua
    for (auto& file : config.project_files) {
        if (!projectloader_service.load_file(file)) {
            std::cout << "Project file \"" << file << "\" did not execute correctly"<< std::endl;
            run_megamol = false;

            // if interactive, continue to run MegaMol
            if (config.interactive) {
                std::cout << "Interactive mode: start MegaMol anyway"<< std::endl;
                run_megamol = true;
            }
        }
    }

    while (run_megamol) {
        run_megamol = render_next_frame();
    }

    graph.Clear();

    // close glfw context, network connections, other system resources
    services.close();

    return 0;
}

<|endoftext|>


<commit_before>// This file is distributed under the MIT license.
// See the LICENSE file for details.

#include <visionaray/morton.h>

#include <gtest/gtest.h>

using namespace visionaray;

TEST(Morton, Encode2D)
{
    int z;

    z = morton_encode2D(0, 0);
    ASSERT_EQ(z, 0);
    z = morton_encode2D(1, 0);
    ASSERT_EQ(z, 1);
    z = morton_encode2D(0, 1);
    ASSERT_EQ(z, 2);
    z = morton_encode2D(1, 1);
    ASSERT_EQ(z, 3);

    z = morton_encode2D(2, 0);
    ASSERT_EQ(z, 4);
    z = morton_encode2D(3, 0);
    ASSERT_EQ(z, 5);
    z = morton_encode2D(2, 1);
    ASSERT_EQ(z, 6);
    z = morton_encode2D(3, 1);
    ASSERT_EQ(z, 7);

    z = morton_encode2D(0, 2);
    ASSERT_EQ(z, 8);
    z = morton_encode2D(1, 2);
    ASSERT_EQ(z, 9);
    z = morton_encode2D(0, 3);
    ASSERT_EQ(z, 10);
    z = morton_encode2D(1, 3);
    ASSERT_EQ(z, 11);
}

TEST(Morton, Encode3D)
{
    int z;

    z = morton_encode3D(0, 0, 0);
    ASSERT_EQ(z, 0);
    z = morton_encode3D(1, 0, 0);
    ASSERT_EQ(z, 1);
    z = morton_encode3D(0, 1, 0);
    ASSERT_EQ(z, 2);
    z = morton_encode3D(1, 1, 0);
    ASSERT_EQ(z, 3);

    z = morton_encode3D(0, 0, 1);
    ASSERT_EQ(z, 4);
    z = morton_encode3D(1, 0, 1);
    ASSERT_EQ(z, 5);
    z = morton_encode3D(0, 1, 1);
    ASSERT_EQ(z, 6);
    z = morton_encode3D(1, 1, 1);
    ASSERT_EQ(z, 7);
}

TEST(Morton, Decode2D)
{
    vec2i p;

    p = morton_decode2D(0);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 0);

    p = morton_decode2D(1);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 0);

    p = morton_decode2D(2);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 1);

    p = morton_decode2D(3);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 1);

    p = morton_decode2D(4);
    ASSERT_EQ(p.x, 2);
    ASSERT_EQ(p.y, 0);

    p = morton_decode2D(5);
    ASSERT_EQ(p.x, 3);
    ASSERT_EQ(p.y, 0);

    p = morton_decode2D(6);
    ASSERT_EQ(p.x, 2);
    ASSERT_EQ(p.y, 1);

    p = morton_decode2D(7);
    ASSERT_EQ(p.x, 3);
    ASSERT_EQ(p.y, 1);

    p = morton_decode2D(8);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 2);

    p = morton_decode2D(9);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 2);

    p = morton_decode2D(10);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 3);

    p = morton_decode2D(11);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 3);
}

TEST(Morton, Decode3D)
{
    vec3i p;

    p = morton_decode3D(0);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 0);
    ASSERT_EQ(p.z, 0);

    p = morton_decode3D(1);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 0);
    ASSERT_EQ(p.z, 0);

    p = morton_decode3D(2);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 1);
    ASSERT_EQ(p.z, 0);

    p = morton_decode3D(3);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 1);
    ASSERT_EQ(p.z, 0);

    p = morton_decode3D(4);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 0);
    ASSERT_EQ(p.z, 1);

    p = morton_decode3D(5);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 0);
    ASSERT_EQ(p.z, 1);

    p = morton_decode3D(6);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 1);
    ASSERT_EQ(p.z, 1);

    p = morton_decode3D(7);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 1);
    ASSERT_EQ(p.z, 1);
}
<commit_msg>Unit tests<commit_after>// This file is distributed under the MIT license.
// See the LICENSE file for details.

#include <visionaray/morton.h>

#include <gtest/gtest.h>

using namespace visionaray;

TEST(Morton, Encode2D)
{
    int z;

    z = morton_encode2D(0, 0);
    ASSERT_EQ(z, 0);
    z = morton_encode2D(1, 0);
    ASSERT_EQ(z, 1);
    z = morton_encode2D(0, 1);
    ASSERT_EQ(z, 2);
    z = morton_encode2D(1, 1);
    ASSERT_EQ(z, 3);

    z = morton_encode2D(2, 0);
    ASSERT_EQ(z, 4);
    z = morton_encode2D(3, 0);
    ASSERT_EQ(z, 5);
    z = morton_encode2D(2, 1);
    ASSERT_EQ(z, 6);
    z = morton_encode2D(3, 1);
    ASSERT_EQ(z, 7);

    z = morton_encode2D(0, 2);
    ASSERT_EQ(z, 8);
    z = morton_encode2D(1, 2);
    ASSERT_EQ(z, 9);
    z = morton_encode2D(0, 3);
    ASSERT_EQ(z, 10);
    z = morton_encode2D(1, 3);
    ASSERT_EQ(z, 11);

    // Diagonal, 2^0..2^29
    for (int i = 0; i < 30; ++i)
    {
        z = morton_encode2D(2 << i, 2 << i);
        ASSERT_EQ(z, (2 << i) * (2 << i) * 3);
    }
}

TEST(Morton, Encode3D)
{
    int z;

    z = morton_encode3D(0, 0, 0);
    ASSERT_EQ(z, 0);
    z = morton_encode3D(1, 0, 0);
    ASSERT_EQ(z, 1);
    z = morton_encode3D(0, 1, 0);
    ASSERT_EQ(z, 2);
    z = morton_encode3D(1, 1, 0);
    ASSERT_EQ(z, 3);

    z = morton_encode3D(0, 0, 1);
    ASSERT_EQ(z, 4);
    z = morton_encode3D(1, 0, 1);
    ASSERT_EQ(z, 5);
    z = morton_encode3D(0, 1, 1);
    ASSERT_EQ(z, 6);
    z = morton_encode3D(1, 1, 1);
    ASSERT_EQ(z, 7);
}

TEST(Morton, Decode2D)
{
    vec2i p;

    p = morton_decode2D(0);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 0);

    p = morton_decode2D(1);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 0);

    p = morton_decode2D(2);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 1);

    p = morton_decode2D(3);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 1);

    p = morton_decode2D(4);
    ASSERT_EQ(p.x, 2);
    ASSERT_EQ(p.y, 0);

    p = morton_decode2D(5);
    ASSERT_EQ(p.x, 3);
    ASSERT_EQ(p.y, 0);

    p = morton_decode2D(6);
    ASSERT_EQ(p.x, 2);
    ASSERT_EQ(p.y, 1);

    p = morton_decode2D(7);
    ASSERT_EQ(p.x, 3);
    ASSERT_EQ(p.y, 1);

    p = morton_decode2D(8);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 2);

    p = morton_decode2D(9);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 2);

    p = morton_decode2D(10);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 3);

    p = morton_decode2D(11);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 3);
}

TEST(Morton, Decode3D)
{
    vec3i p;

    p = morton_decode3D(0);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 0);
    ASSERT_EQ(p.z, 0);

    p = morton_decode3D(1);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 0);
    ASSERT_EQ(p.z, 0);

    p = morton_decode3D(2);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 1);
    ASSERT_EQ(p.z, 0);

    p = morton_decode3D(3);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 1);
    ASSERT_EQ(p.z, 0);

    p = morton_decode3D(4);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 0);
    ASSERT_EQ(p.z, 1);

    p = morton_decode3D(5);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 0);
    ASSERT_EQ(p.z, 1);

    p = morton_decode3D(6);
    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 1);
    ASSERT_EQ(p.z, 1);

    p = morton_decode3D(7);
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 1);
    ASSERT_EQ(p.z, 1);
}
<|endoftext|>


<commit_before>#include "gtest/gtest.h"
#include "gen_image.hpp"
#include "batchfile.hpp"

#include "params.hpp"
#include "etl_interface.hpp"
#include "etl_image.hpp"
#include "etl_label_test.hpp"
#include "etl_bbox.hpp"
#include "etl_lmap.hpp"
#include "provider.hpp"
#include "json.hpp"
#include "media.hpp"
#include "batchfile.hpp"
#include "util.hpp"

extern gen_image image_dataset;

using namespace std;
using namespace nervana;

TEST(provider,image) {
    nlohmann::json js = {{"media","image"},
                         {"data_config",{{"height",128},{"width",128},{"channel_major",false},{"flip",true}}},
                         {"target_config",{}}};
    cout << js.dump(4) << endl;
    shared_ptr<nervana::train_base> media = Media::create(js);

    auto data_config = js["data_config"];
    int height = data_config["height"];
    int width = data_config["width"];
    size_t dsize = height * width * 3;
    size_t tsize = 4;

    vector<char> dbuffer(dsize);
    vector<char> tbuffer(tsize);

    auto files = image_dataset.GetFiles();
    ASSERT_NE(0,files.size());

    BatchFileReader reader(files[0]);
    for (int i=0; i<reader.itemCount()/2; i++ ) {
        shared_ptr<vector<char>> data = reader.read();
        shared_ptr<vector<char>> target = reader.read();
        Buffer data_p(&(*data)[0],data->size());
        Buffer target_p(&(*target)[0],target->size());
        BufferPair bp(&data_p, &target_p);
        media->provide_pair(0,&bp,&dbuffer[0],&tbuffer[0]);

        int target_value = unpack_le<int>(&tbuffer[0]);
        EXPECT_EQ(42+i,target_value);
//        cv::Mat mat(width,height,CV_8UC3,&dbuffer[0]);
//        string filename = "data" + to_string(i) + ".png";
//        cv::imwrite(filename,mat);
    }
    cout << "cpio contains " << reader.itemCount() << endl;
}

TEST(provider, argtype) {

    {
        /* Create extractor with default num channels param */
        string cfgString = "{\"height\":10, \"width\":10}";
        auto js = nlohmann::json::parse(cfgString);
        auto cfg = make_shared<image::config>();
        cfg->set_config(js);
        auto ic = make_shared<image::extractor>(cfg);
        EXPECT_EQ(ic->get_channel_count(), 3);
    }

    {
        string cfgString = R"(
            {
                "height": 30,
                "width" : 30,
                "distribution":{
                    "angle" : [-20, 20],
                    "scale" : [0.2, 0.8],
                    "lighting" : [0.0, 0.1],
                    "aspect_ratio" : [0.75, 1.33],
                    "flip" : [false]
                }
            }
        )";

        auto itpj = make_shared<image::config>();
        itpj->set_config(nlohmann::json::parse(cfgString));

        // output the fixed parameters
        EXPECT_EQ(30,itpj->height);
        EXPECT_EQ(30,itpj->width);

        // output the random parameters
        default_random_engine r_eng(0);
        image::param_factory img_prm_maker(itpj, r_eng);
        auto imgt = make_shared<image::transformer>(itpj);

        auto input_img_ptr = make_shared<image::decoded>(cv::Mat(256, 320, CV_8UC3));

        auto its = img_prm_maker.make_params(input_img_ptr);
        its->dump();
    }

    {
        string cfgString = R"(
            {
                "extract offset":  20,
                "dist_params/transform scale":  [-8, 8],
                "dist_params/transform shift":  [-5, 5]
            }
        )";
        auto lblcfg = make_shared<label_test::config>();
        lblcfg->set_config(nlohmann::json::parse(cfgString));

        auto dataFiles = image_dataset.GetFiles();
        ASSERT_GT(dataFiles.size(),0);
        string batchFileName = dataFiles[0];
        BatchFileReader bf(batchFileName);

        // Just get a single item
        Buffer data(0);
        Buffer labels(0);
        bf.read(data);
        bf.read(labels);

        bf.close();

        default_random_engine r_eng(0);
        default_random_engine r_eng_copy(0);
        auto prm_fcty = make_shared<label_test::param_factory>(lblcfg, r_eng);
        label_test::param_factory lbl_prm_maker(lblcfg, r_eng_copy);

        // Note we don't need the media to get params
        auto lstg = lbl_prm_maker.make_params(nullptr);

        int reference = ((int) *((int*)labels._data) + lblcfg->ex_offset)* lstg->scale + lstg->shift;

        // Take the int and do provision with it.
        auto lble = make_shared<label_test::extractor>(lblcfg);
        auto lblt = make_shared<label_test::transformer>(lblcfg);

        {
            auto lbll = make_shared<label_test::loader>(lblcfg);

            int reference_target = reference;
            int loaded_target = 0;
            provider<label_test::decoded, label_test::params> pp{lble, lblt, lbll, prm_fcty};
            auto ls2 = pp.provide(labels_data, 4, (char *)(&loaded_target), 4, nullptr);
            EXPECT_EQ(ls2->scale, lstg->scale);
            EXPECT_EQ(ls2->shift, lstg->shift);
            EXPECT_EQ(reference_target, loaded_target);
        }

        {
            // This is a float loader that loads into a float with an offset
            string lArgString = R"(
                {
                    "load do float":  true,
                    "load offset": 0.8
                }
            )";
            auto flt_lbl_config = make_shared<label_test::config>();
            flt_lbl_config->set_config(nlohmann::json::parse(lArgString));

            auto lbll = make_shared<label_test::loader>(flt_lbl_config);

            float reference_target = reference + 0.8;
            float loaded_target = 0.0;
            provider<label_test::decoded, label_test::params> pp{lble, lblt, lbll};
            pp.provide(labels._data, 4, (char *)(&loaded_target), 4, lstg);
            EXPECT_EQ(reference_target, loaded_target);
        }
    }
}

<commit_msg>fix issues with rebase<commit_after>#include "gtest/gtest.h"
#include "gen_image.hpp"
#include "batchfile.hpp"

#include "params.hpp"
#include "etl_interface.hpp"
#include "etl_image.hpp"
#include "etl_label_test.hpp"
#include "etl_bbox.hpp"
#include "etl_lmap.hpp"
#include "provider.hpp"
#include "json.hpp"
#include "media.hpp"
#include "batchfile.hpp"
#include "util.hpp"

extern gen_image image_dataset;

using namespace std;
using namespace nervana;

TEST(provider,image) {
    nlohmann::json js = {{"media","image"},
                         {"data_config",{{"height",128},{"width",128},{"channel_major",false},{"flip",true}}},
                         {"target_config",{}}};
    cout << js.dump(4) << endl;
    shared_ptr<nervana::train_base> media = Media::create(js);

    auto data_config = js["data_config"];
    int height = data_config["height"];
    int width = data_config["width"];
    size_t dsize = height * width * 3;
    size_t tsize = 4;

    vector<char> dbuffer(dsize);
    vector<char> tbuffer(tsize);

    auto files = image_dataset.GetFiles();
    ASSERT_NE(0,files.size());

    BatchFileReader reader(files[0]);
    for (int i=0; i<reader.itemCount()/2; i++ ) {
        Buffer data_p(0);
        reader.read(data_p);

        Buffer target_p(0);
        reader.read(target_p);

        BufferPair bp(&data_p, &target_p);
        media->provide_pair(0,&bp,&dbuffer[0],&tbuffer[0]);

        int target_value = unpack_le<int>(&tbuffer[0]);
        EXPECT_EQ(42+i,target_value);
//        cv::Mat mat(width,height,CV_8UC3,&dbuffer[0]);
//        string filename = "data" + to_string(i) + ".png";
//        cv::imwrite(filename,mat);
    }
    cout << "cpio contains " << reader.itemCount() << endl;
}

TEST(provider, argtype) {

    {
        /* Create extractor with default num channels param */
        string cfgString = "{\"height\":10, \"width\":10}";
        auto js = nlohmann::json::parse(cfgString);
        auto cfg = make_shared<image::config>();
        cfg->set_config(js);
        auto ic = make_shared<image::extractor>(cfg);
        EXPECT_EQ(ic->get_channel_count(), 3);
    }

    {
        string cfgString = R"(
            {
                "height": 30,
                "width" : 30,
                "distribution":{
                    "angle" : [-20, 20],
                    "scale" : [0.2, 0.8],
                    "lighting" : [0.0, 0.1],
                    "aspect_ratio" : [0.75, 1.33],
                    "flip" : [false]
                }
            }
        )";

        auto itpj = make_shared<image::config>();
        itpj->set_config(nlohmann::json::parse(cfgString));

        // output the fixed parameters
        EXPECT_EQ(30,itpj->height);
        EXPECT_EQ(30,itpj->width);

        // output the random parameters
        default_random_engine r_eng(0);
        image::param_factory img_prm_maker(itpj, r_eng);
        auto imgt = make_shared<image::transformer>(itpj);

        auto input_img_ptr = make_shared<image::decoded>(cv::Mat(256, 320, CV_8UC3));

        auto its = img_prm_maker.make_params(input_img_ptr);
        its->dump();
    }

    {
        string cfgString = R"(
            {
                "extract offset":  20,
                "dist_params/transform scale":  [-8, 8],
                "dist_params/transform shift":  [-5, 5]
            }
        )";
        auto lblcfg = make_shared<label_test::config>();
        lblcfg->set_config(nlohmann::json::parse(cfgString));

        auto dataFiles = image_dataset.GetFiles();
        ASSERT_GT(dataFiles.size(),0);
        string batchFileName = dataFiles[0];
        BatchFileReader bf(batchFileName);

        // Just get a single item
        Buffer data(0);
        Buffer labels(0);
        bf.read(data);
        bf.read(labels);

        bf.close();

        default_random_engine r_eng(0);
        default_random_engine r_eng_copy(0);
        auto prm_fcty = make_shared<label_test::param_factory>(lblcfg, r_eng);
        label_test::param_factory lbl_prm_maker(lblcfg, r_eng_copy);

        // Note we don't need the media to get params
        auto lstg = lbl_prm_maker.make_params(nullptr);

        int reference = ((int) *((int*)labels._data) + lblcfg->ex_offset)* lstg->scale + lstg->shift;

        // Take the int and do provision with it.
        auto lble = make_shared<label_test::extractor>(lblcfg);
        auto lblt = make_shared<label_test::transformer>(lblcfg);

        {
            auto lbll = make_shared<label_test::loader>(lblcfg);

            int reference_target = reference;
            int loaded_target = 0;
            provider<label_test::decoded, label_test::params> pp{lble, lblt, lbll, prm_fcty};
            auto ls2 = pp.provide(labels._data, 4, (char *)(&loaded_target), 4, nullptr);
            EXPECT_EQ(ls2->scale, lstg->scale);
            EXPECT_EQ(ls2->shift, lstg->shift);
            EXPECT_EQ(reference_target, loaded_target);
        }

        {
            // This is a float loader that loads into a float with an offset
            string lArgString = R"(
                {
                    "load do float":  true,
                    "load offset": 0.8
                }
            )";
            auto flt_lbl_config = make_shared<label_test::config>();
            flt_lbl_config->set_config(nlohmann::json::parse(lArgString));

            auto lbll = make_shared<label_test::loader>(flt_lbl_config);

            float reference_target = reference + 0.8;
            float loaded_target = 0.0;
            provider<label_test::decoded, label_test::params> pp{lble, lblt, lbll};
            pp.provide(labels._data, 4, (char *)(&loaded_target), 4, lstg);
            EXPECT_EQ(reference_target, loaded_target);
        }
    }
}

<|endoftext|>


<commit_before>/**
 * This file is part of the CernVM File System.
 */

#include <algorithm>

#include <cerrno>
#include <gtest/gtest.h>

#include "../../cvmfs/platform.h"
#include "../../cvmfs/util.h"
#include "../../cvmfs/xattr.h"

using namespace std;  // NOLINT

class T_Xattr : public ::testing::Test {
 protected:
  virtual void SetUp() {
    default_list.Set("keya", "valuea");
    default_list.Set("keyb", "valueb");
    default_list.Set("empty_key", "");
  }

  unsigned int CountAttributesInFile(const std::string &path) const {
    ssize_t sz_list = platform_llistxattr(path.c_str(), NULL, 0);
    if (sz_list <= 0) {
      return 0;
    }

    char *list = reinterpret_cast<char *>(malloc(sz_list));
    sz_list = platform_llistxattr(path.c_str(), list, sz_list);
    if (sz_list <= 0) {
      return 0;
    }

    const std::string attrs(list, sz_list);
    free(list);
    return std::count(attrs.begin(), attrs.end(), '\0');
  }

  XattrList default_list;
};

TEST_F(T_Xattr, CreateFromFile) {
  EXPECT_EQ(NULL, XattrList::CreateFromFile("/no/such/file"));

  // Create extended attributes
  string tmp_path;
  FILE *f = CreateTempFile("./cvmfs_ut_xattr", 0600, "w", &tmp_path);
  ASSERT_TRUE(f != NULL);
  fclose(f);
  UnlinkGuard unlink_guard(tmp_path);

  // check if xattr is supported on the current platform
  const bool success = platform_setxattr(tmp_path, "user.check", "foo");
  if (!success && errno == EOPNOTSUPP) {
    SUCCEED() << "extended attributes are not supported on " << tmp_path;
    return;
  }

  const unsigned int default_attrs = CountAttributesInFile(tmp_path);

  UniquePtr<XattrList> from_file1(XattrList::CreateFromFile(tmp_path));
  ASSERT_TRUE(from_file1.IsValid());
  EXPECT_EQ(default_attrs, from_file1->ListKeys().size());

  string value;
  ASSERT_TRUE(platform_setxattr(tmp_path, "user.test", "value"))
    << "failed to set user defined extended attribute (errno: " << errno << ")";
  UniquePtr<XattrList> from_file2(XattrList::CreateFromFile(tmp_path));
  ASSERT_TRUE(from_file2.IsValid());
  EXPECT_EQ(default_attrs + 1, from_file2->ListKeys().size());
  EXPECT_TRUE(from_file2->Get("user.test", &value));
  EXPECT_EQ("value", value);

#ifndef __APPLE__
  ASSERT_TRUE(platform_setxattr(tmp_path, "user.test2", "value2"));
  string long_string = "user." + string(250, 'x');
  string too_long_string = "user." + string(300, 'x');
  ASSERT_TRUE(platform_setxattr(tmp_path, long_string, long_string));
  ASSERT_TRUE(platform_setxattr(tmp_path, "user.test3", too_long_string));
  UniquePtr<XattrList> from_file3(XattrList::CreateFromFile(tmp_path));
  ASSERT_TRUE(from_file3.IsValid());
  EXPECT_EQ(default_attrs + 3, from_file3->ListKeys().size());
  EXPECT_TRUE(from_file3->Get("user.test", &value));
  EXPECT_EQ("value", value);
  EXPECT_TRUE(from_file3->Get("user.test2", &value));
  EXPECT_EQ("value2", value);
  EXPECT_TRUE(from_file3->Get(long_string, &value));
  EXPECT_EQ(long_string, value);
#endif
}

TEST_F(T_Xattr, Deserialize) {
  unsigned char *buf;
  unsigned size;
  default_list.Serialize(&buf, &size);
  EXPECT_GT(size, 0U);
  EXPECT_TRUE(buf != NULL);

  UniquePtr<XattrList> xattr_list(XattrList::Deserialize(buf, size));
  free(buf);
  ASSERT_TRUE(xattr_list.IsValid());
  EXPECT_EQ(3U, xattr_list->ListKeys().size());
  string value;
  EXPECT_TRUE(xattr_list->Get("keya", &value));
  EXPECT_EQ("valuea", value);
  EXPECT_TRUE(xattr_list->Get("keyb", &value));
  EXPECT_EQ("valueb", value);
  EXPECT_TRUE(xattr_list->Get("empty_key", &value));
  EXPECT_EQ("", value);
}

TEST_F(T_Xattr, DeserializeNull) {
  UniquePtr<XattrList> xattr_list(XattrList::Deserialize(NULL, 0));
  ASSERT_TRUE(xattr_list.IsValid());
  EXPECT_EQ(0U, xattr_list->ListKeys().size());
}

TEST_F(T_Xattr, DeserializeInvalid) {
  unsigned char *buf;
  unsigned size;
  default_list.Serialize(&buf, &size);
  EXPECT_GT(size, 0U);
  EXPECT_TRUE(buf != NULL);

  UniquePtr<XattrList> xl1(XattrList::Deserialize(buf, 0));
  EXPECT_FALSE(xl1.IsValid());

  buf[0] = 255;
  UniquePtr<XattrList> xl2(XattrList::Deserialize(buf, size));
  EXPECT_FALSE(xl2.IsValid());
  buf[0] = XattrList::kVersion;

  UniquePtr<XattrList> xl3(XattrList::Deserialize(buf, 3));
  EXPECT_FALSE(xl3.IsValid());

  UniquePtr<XattrList> xl4(XattrList::Deserialize(buf, 5));
  EXPECT_FALSE(xl4.IsValid());

  unsigned char save0, save1;
  save0 = buf[2];
  save1 = buf[3];
  buf[2] = 0;
  buf[3] = 0;
  UniquePtr<XattrList> xl5(XattrList::Deserialize(buf, size));
  EXPECT_FALSE(xl5.IsValid());
  buf[3] = save1;
  UniquePtr<XattrList> xl6(XattrList::Deserialize(buf, size));
  EXPECT_FALSE(xl6.IsValid());

  buf[2] = save0;
  UniquePtr<XattrList> xl7(XattrList::Deserialize(buf, size));
  EXPECT_TRUE(xl7.IsValid());

  free(buf);
}

TEST_F(T_Xattr, Get) {
  string value;
  EXPECT_TRUE(default_list.Get("keya", &value));
  EXPECT_EQ("valuea", value);
  EXPECT_TRUE(default_list.Get("empty_key", &value));
  EXPECT_EQ("", value);
  EXPECT_FALSE(default_list.Get("not here", &value));
}

TEST_F(T_Xattr, ListKeys) {
  XattrList empty;
  EXPECT_TRUE(empty.ListKeys().empty());
  ASSERT_EQ(3U, default_list.ListKeys().size());
  EXPECT_EQ("empty_key", default_list.ListKeys()[0]);
  EXPECT_EQ("keya", default_list.ListKeys()[1]);
  EXPECT_EQ("keyb", default_list.ListKeys()[2]);
}

TEST_F(T_Xattr, ListKeysPosix) {
  const char base_list[] = "user.a\0user.b\0keya\0";
  string existing_list(base_list, sizeof(base_list)-1);
  XattrList empty;
  EXPECT_EQ("", empty.ListKeysPosix(""));
  const char expect1[] = "user.a\0user.b\0keya\0";
  EXPECT_EQ(string(expect1, sizeof(expect1)-1),
            empty.ListKeysPosix(existing_list));
  const char expect2[] = "empty_key\0keya\0keyb\0";
  EXPECT_EQ(string(expect2, sizeof(expect2)-1),
            default_list.ListKeysPosix(""));
  const char expect3[] = "user.a\0user.b\0empty_key\0keya\0keyb\0";
  EXPECT_EQ(string(expect3, sizeof(expect3)-1),
            default_list.ListKeysPosix(existing_list));
}

TEST_F(T_Xattr, Set) {
  string value;
  // Set a key
  EXPECT_TRUE(default_list.Set("keyc", "valuec"));
  EXPECT_TRUE(default_list.Get("keyc", &value));
  EXPECT_EQ("valuec", value);
  // Change a key
  EXPECT_TRUE(default_list.Set("keyc", ""));
  EXPECT_TRUE(default_list.Get("keyc", &value));
  EXPECT_EQ("", value);

  // Invalid operations
  string longstring(257, 'a');
  EXPECT_FALSE(default_list.Set(longstring, "value"));
  EXPECT_FALSE(default_list.Set("key", longstring));
  string nullstring(1, '\0');
  EXPECT_FALSE(default_list.Set(nullstring, "value"));

  XattrList new_list;
  for (unsigned i = 0; i < 256; ++i) {
    EXPECT_TRUE(new_list.Set(StringifyInt(i), ""));
  }
  EXPECT_FALSE(new_list.Set("256", ""));
}

TEST_F(T_Xattr, Remove) {
  string value;
  EXPECT_TRUE(default_list.Get("keya", &value));
  EXPECT_TRUE(default_list.Remove("keya"));
  EXPECT_FALSE(default_list.Get("keya", &value));
  EXPECT_FALSE(default_list.Remove("keya"));
}

TEST_F(T_Xattr, IsEmpty) {
  EXPECT_TRUE(XattrList().IsEmpty());
  EXPECT_FALSE(default_list.IsEmpty());
  vector<string> keys = default_list.ListKeys();
  for (unsigned i = 0; i < keys.size(); ++i) {
    EXPECT_TRUE(default_list.Remove(keys[i]));
  }
  EXPECT_TRUE(default_list.IsEmpty());
}

// The normal serialization part is tested as part of Deserialize
TEST_F(T_Xattr, SerializeNull) {
  unsigned char *buf;
  unsigned size;
  XattrList empty_list;
  empty_list.Serialize(&buf, &size);
  EXPECT_EQ(NULL, buf);
  EXPECT_EQ(0U, size);
}

TEST_F(T_Xattr, Limits) {
  XattrList large_list;
  string large_value(256, 'a');
  for (unsigned i = 0; i < 256; ++i) {
    char hex[3];
    snprintf(hex, sizeof(hex), "%02x", i);
    string large_key(128, hex[0]);
    large_key += string(128, hex[1]);
    EXPECT_TRUE(large_list.Set(large_key, large_value));
  }
  EXPECT_EQ(256U, large_list.ListKeys().size());
  for (unsigned i = 0; i < 256; ++i) {
    string value;
    char hex[3];
    snprintf(hex, sizeof(hex), "%02x", i);
    string large_key(128, hex[0]);
    large_key += string(128, hex[1]);
    EXPECT_TRUE(large_list.Get(large_key, &value));
    EXPECT_EQ(large_value, value);
  }
}
<commit_msg>TEST: Amend T_Xattr for Xattr::Has()<commit_after>/**
 * This file is part of the CernVM File System.
 */

#include <algorithm>

#include <cerrno>
#include <gtest/gtest.h>

#include "../../cvmfs/platform.h"
#include "../../cvmfs/util.h"
#include "../../cvmfs/xattr.h"

using namespace std;  // NOLINT

class T_Xattr : public ::testing::Test {
 protected:
  virtual void SetUp() {
    default_list.Set("keya", "valuea");
    default_list.Set("keyb", "valueb");
    default_list.Set("empty_key", "");
  }

  unsigned int CountAttributesInFile(const std::string &path) const {
    ssize_t sz_list = platform_llistxattr(path.c_str(), NULL, 0);
    if (sz_list <= 0) {
      return 0;
    }

    char *list = reinterpret_cast<char *>(malloc(sz_list));
    sz_list = platform_llistxattr(path.c_str(), list, sz_list);
    if (sz_list <= 0) {
      return 0;
    }

    const std::string attrs(list, sz_list);
    free(list);
    return std::count(attrs.begin(), attrs.end(), '\0');
  }

  XattrList default_list;
};

TEST_F(T_Xattr, CreateFromFile) {
  EXPECT_EQ(NULL, XattrList::CreateFromFile("/no/such/file"));

  // Create extended attributes
  string tmp_path;
  FILE *f = CreateTempFile("./cvmfs_ut_xattr", 0600, "w", &tmp_path);
  ASSERT_TRUE(f != NULL);
  fclose(f);
  UnlinkGuard unlink_guard(tmp_path);

  // check if xattr is supported on the current platform
  const bool success = platform_setxattr(tmp_path, "user.check", "foo");
  if (!success && errno == EOPNOTSUPP) {
    SUCCEED() << "extended attributes are not supported on " << tmp_path;
    return;
  }

  const unsigned int default_attrs = CountAttributesInFile(tmp_path);

  UniquePtr<XattrList> from_file1(XattrList::CreateFromFile(tmp_path));
  ASSERT_TRUE(from_file1.IsValid());
  EXPECT_EQ(default_attrs, from_file1->ListKeys().size());

  string value;
  ASSERT_TRUE(platform_setxattr(tmp_path, "user.test", "value"))
    << "failed to set user defined extended attribute (errno: " << errno << ")";
  UniquePtr<XattrList> from_file2(XattrList::CreateFromFile(tmp_path));
  ASSERT_TRUE(from_file2.IsValid());
  EXPECT_EQ(default_attrs + 1, from_file2->ListKeys().size());
  EXPECT_TRUE(from_file2->Get("user.test", &value));
  EXPECT_TRUE(from_file2->Has("user.test"));
  EXPECT_EQ("value", value);

#ifndef __APPLE__
  ASSERT_TRUE(platform_setxattr(tmp_path, "user.test2", "value2"));
  string long_string = "user." + string(250, 'x');
  string too_long_string = "user." + string(300, 'x');
  ASSERT_TRUE(platform_setxattr(tmp_path, long_string, long_string));
  ASSERT_TRUE(platform_setxattr(tmp_path, "user.test3", too_long_string));
  UniquePtr<XattrList> from_file3(XattrList::CreateFromFile(tmp_path));
  ASSERT_TRUE(from_file3.IsValid());
  EXPECT_EQ(default_attrs + 3, from_file3->ListKeys().size());
  EXPECT_TRUE(from_file3->Get("user.test", &value));
  EXPECT_TRUE(from_file3->Has("user.test"));
  EXPECT_EQ("value", value);
  EXPECT_TRUE(from_file3->Get("user.test2", &value));
  EXPECT_TRUE(from_file3->Has("user.test2"));
  EXPECT_EQ("value2", value);
  EXPECT_TRUE(from_file3->Get(long_string, &value));
  EXPECT_TRUE(from_file3->Has(long_string));
  EXPECT_EQ(long_string, value);
#endif
}

TEST_F(T_Xattr, Deserialize) {
  unsigned char *buf;
  unsigned size;
  default_list.Serialize(&buf, &size);
  EXPECT_GT(size, 0U);
  EXPECT_TRUE(buf != NULL);

  UniquePtr<XattrList> xattr_list(XattrList::Deserialize(buf, size));
  free(buf);
  ASSERT_TRUE(xattr_list.IsValid());
  EXPECT_EQ(3U, xattr_list->ListKeys().size());
  string value;
  EXPECT_TRUE(xattr_list->Get("keya", &value));
  EXPECT_TRUE(xattr_list->Has("keya"));
  EXPECT_EQ("valuea", value);
  EXPECT_TRUE(xattr_list->Get("keyb", &value));
  EXPECT_TRUE(xattr_list->Has("keyb"));
  EXPECT_EQ("valueb", value);
  EXPECT_TRUE(xattr_list->Get("empty_key", &value));
  EXPECT_TRUE(xattr_list->Has("empty_key"));
  EXPECT_EQ("", value);
}

TEST_F(T_Xattr, DeserializeNull) {
  UniquePtr<XattrList> xattr_list(XattrList::Deserialize(NULL, 0));
  ASSERT_TRUE(xattr_list.IsValid());
  EXPECT_EQ(0U, xattr_list->ListKeys().size());
}

TEST_F(T_Xattr, DeserializeInvalid) {
  unsigned char *buf;
  unsigned size;
  default_list.Serialize(&buf, &size);
  EXPECT_GT(size, 0U);
  EXPECT_TRUE(buf != NULL);

  UniquePtr<XattrList> xl1(XattrList::Deserialize(buf, 0));
  EXPECT_FALSE(xl1.IsValid());

  buf[0] = 255;
  UniquePtr<XattrList> xl2(XattrList::Deserialize(buf, size));
  EXPECT_FALSE(xl2.IsValid());
  buf[0] = XattrList::kVersion;

  UniquePtr<XattrList> xl3(XattrList::Deserialize(buf, 3));
  EXPECT_FALSE(xl3.IsValid());

  UniquePtr<XattrList> xl4(XattrList::Deserialize(buf, 5));
  EXPECT_FALSE(xl4.IsValid());

  unsigned char save0, save1;
  save0 = buf[2];
  save1 = buf[3];
  buf[2] = 0;
  buf[3] = 0;
  UniquePtr<XattrList> xl5(XattrList::Deserialize(buf, size));
  EXPECT_FALSE(xl5.IsValid());
  buf[3] = save1;
  UniquePtr<XattrList> xl6(XattrList::Deserialize(buf, size));
  EXPECT_FALSE(xl6.IsValid());

  buf[2] = save0;
  UniquePtr<XattrList> xl7(XattrList::Deserialize(buf, size));
  EXPECT_TRUE(xl7.IsValid());

  free(buf);
}

TEST_F(T_Xattr, Has) {
  string value;
  EXPECT_TRUE(default_list.Has("keya"));
  EXPECT_TRUE(default_list.Has("empty_key"));
  EXPECT_FALSE(default_list.Has("not here"));
}

TEST_F(T_Xattr, Get) {
  string value;
  EXPECT_TRUE(default_list.Get("keya", &value));
  EXPECT_EQ("valuea", value);
  EXPECT_TRUE(default_list.Get("empty_key", &value));
  EXPECT_EQ("", value);
  EXPECT_FALSE(default_list.Get("not here", &value));
}

TEST_F(T_Xattr, ListKeys) {
  XattrList empty;
  EXPECT_TRUE(empty.ListKeys().empty());
  ASSERT_EQ(3U, default_list.ListKeys().size());
  EXPECT_EQ("empty_key", default_list.ListKeys()[0]);
  EXPECT_EQ("keya", default_list.ListKeys()[1]);
  EXPECT_EQ("keyb", default_list.ListKeys()[2]);
}

TEST_F(T_Xattr, ListKeysPosix) {
  const char base_list[] = "user.a\0user.b\0keya\0";
  string existing_list(base_list, sizeof(base_list)-1);
  XattrList empty;
  EXPECT_EQ("", empty.ListKeysPosix(""));
  const char expect1[] = "user.a\0user.b\0keya\0";
  EXPECT_EQ(string(expect1, sizeof(expect1)-1),
            empty.ListKeysPosix(existing_list));
  const char expect2[] = "empty_key\0keya\0keyb\0";
  EXPECT_EQ(string(expect2, sizeof(expect2)-1),
            default_list.ListKeysPosix(""));
  const char expect3[] = "user.a\0user.b\0empty_key\0keya\0keyb\0";
  EXPECT_EQ(string(expect3, sizeof(expect3)-1),
            default_list.ListKeysPosix(existing_list));
}

TEST_F(T_Xattr, Set) {
  string value;
  // Set a key
  EXPECT_TRUE(default_list.Set("keyc", "valuec"));
  EXPECT_TRUE(default_list.Get("keyc", &value));
  EXPECT_EQ("valuec", value);
  // Change a key
  EXPECT_TRUE(default_list.Set("keyc", ""));
  EXPECT_TRUE(default_list.Get("keyc", &value));
  EXPECT_EQ("", value);

  // Invalid operations
  string longstring(257, 'a');
  EXPECT_FALSE(default_list.Set(longstring, "value"));
  EXPECT_FALSE(default_list.Set("key", longstring));
  string nullstring(1, '\0');
  EXPECT_FALSE(default_list.Set(nullstring, "value"));

  XattrList new_list;
  for (unsigned i = 0; i < 256; ++i) {
    EXPECT_TRUE(new_list.Set(StringifyInt(i), ""));
  }
  EXPECT_FALSE(new_list.Set("256", ""));
}

TEST_F(T_Xattr, Remove) {
  string value;
  EXPECT_TRUE(default_list.Has("keya"));
  EXPECT_TRUE(default_list.Get("keya", &value));
  EXPECT_TRUE(default_list.Remove("keya"));
  EXPECT_FALSE(default_list.Get("keya", &value));
  EXPECT_FALSE(default_list.Has("keya"));
  EXPECT_FALSE(default_list.Remove("keya"));
}

TEST_F(T_Xattr, IsEmpty) {
  EXPECT_TRUE(XattrList().IsEmpty());
  EXPECT_FALSE(default_list.IsEmpty());
  vector<string> keys = default_list.ListKeys();
  for (unsigned i = 0; i < keys.size(); ++i) {
    EXPECT_TRUE(default_list.Remove(keys[i]));
  }
  EXPECT_TRUE(default_list.IsEmpty());
}

// The normal serialization part is tested as part of Deserialize
TEST_F(T_Xattr, SerializeNull) {
  unsigned char *buf;
  unsigned size;
  XattrList empty_list;
  empty_list.Serialize(&buf, &size);
  EXPECT_EQ(NULL, buf);
  EXPECT_EQ(0U, size);
}

TEST_F(T_Xattr, Limits) {
  XattrList large_list;
  string large_value(256, 'a');
  for (unsigned i = 0; i < 256; ++i) {
    char hex[3];
    snprintf(hex, sizeof(hex), "%02x", i);
    string large_key(128, hex[0]);
    large_key += string(128, hex[1]);
    EXPECT_TRUE(large_list.Set(large_key, large_value));
  }
  EXPECT_EQ(256U, large_list.ListKeys().size());
  for (unsigned i = 0; i < 256; ++i) {
    string value;
    char hex[3];
    snprintf(hex, sizeof(hex), "%02x", i);
    string large_key(128, hex[0]);
    large_key += string(128, hex[1]);
    EXPECT_TRUE(large_list.Get(large_key, &value));
    EXPECT_EQ(large_value, value);
  }
}
<|endoftext|>


<commit_before>/****************************************************************************
 *
 *   Copyright (c) 2020 PX4 Development Team. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/**
 * @file hover_thrust_estimator.cpp
 *
 * @author Mathieu Bresciani 	<brescianimathieu@gmail.com>
 */

#include "MulticopterHoverThrustEstimator.hpp"

#include <mathlib/mathlib.h>

using namespace time_literals;

MulticopterHoverThrustEstimator::MulticopterHoverThrustEstimator() :
	ModuleParams(nullptr),
	WorkItem(MODULE_NAME, px4::wq_configurations::nav_and_controllers)
{
	_valid_hysteresis.set_hysteresis_time_from(false, 2_s);
	updateParams();
	reset();
}

MulticopterHoverThrustEstimator::~MulticopterHoverThrustEstimator()
{
	perf_free(_cycle_perf);
}

bool MulticopterHoverThrustEstimator::init()
{
	if (!_vehicle_local_position_sub.registerCallback()) {
		PX4_ERR("vehicle_local_position_setpoint callback registration failed!");
		return false;
	}

	return true;
}

void MulticopterHoverThrustEstimator::reset()
{
	_hover_thrust_ekf.setHoverThrust(_param_mpc_thr_hover.get());
	_hover_thrust_ekf.setHoverThrustStdDev(_param_hte_ht_err_init.get());
	_hover_thrust_ekf.resetAccelNoise();
}

void MulticopterHoverThrustEstimator::updateParams()
{
	const float ht_err_init_prev = _param_hte_ht_err_init.get();
	ModuleParams::updateParams();

	_hover_thrust_ekf.setProcessNoiseStdDev(_param_hte_ht_noise.get());

	if (fabsf(_param_hte_ht_err_init.get() - ht_err_init_prev) > FLT_EPSILON) {
		_hover_thrust_ekf.setHoverThrustStdDev(_param_hte_ht_err_init.get());
	}

	_hover_thrust_ekf.setAccelInnovGate(_param_hte_acc_gate.get());
}

void MulticopterHoverThrustEstimator::Run()
{
	if (should_exit()) {
		_vehicle_local_position_sub.unregisterCallback();
		exit_and_cleanup();
		return;
	}

	if (_vehicle_land_detected_sub.updated()) {
		vehicle_land_detected_s vehicle_land_detected;

		if (_vehicle_land_detected_sub.copy(&vehicle_land_detected)) {
			_landed = vehicle_land_detected.landed;

			if (_landed) {
				_in_air = false;
			}
		}
	}

	if (!_vehicle_local_position_sub.updated()) {
		return;
	}

	vehicle_local_position_s local_pos{};

	if (_vehicle_local_position_sub.copy(&local_pos)) {
		// This is only necessary because the landed
		// flag of the land detector does not guarantee that
		// the vehicle does not touch the ground anymore.
		// There is no check for the dist_bottom validity as
		// this value is always good enough after takeoff for
		// this use case.
		// TODO: improve the landed flag
		if (!_landed) {
			if (local_pos.dist_bottom > 1.f) {
				_in_air = true;
			}
		}
	}

	// new local position setpoint needed every iteration
	if (!_vehicle_local_position_setpoint_sub.updated()) {
		return;
	}

	// check for parameter updates
	if (_parameter_update_sub.updated()) {
		// clear update
		parameter_update_s pupdate;
		_parameter_update_sub.copy(&pupdate);

		// update parameters from storage
		updateParams();
	}

	perf_begin(_cycle_perf);

	if (_vehicle_status_sub.updated()) {
		vehicle_status_s vehicle_status;

		if (_vehicle_status_sub.copy(&vehicle_status)) {
			_armed = (vehicle_status.arming_state == vehicle_status_s::ARMING_STATE_ARMED);
		}
	}

	const float dt = (local_pos.timestamp - _timestamp_last) * 1e-6f;
	_timestamp_last = local_pos.timestamp;

	if (_armed && _in_air && (dt > 0.001f) && (dt < 1.f) && PX4_ISFINITE(local_pos.az)) {

		_hover_thrust_ekf.predict(dt);

		vehicle_local_position_setpoint_s local_pos_sp;

		if (_vehicle_local_position_setpoint_sub.copy(&local_pos_sp)) {
			if (PX4_ISFINITE(local_pos_sp.thrust[2])) {
				// Inform the hover thrust estimator about the measured vertical
				// acceleration (positive acceleration is up) and the current thrust (positive thrust is up)
				// Guard against fast up and down motions biasing the estimator due to large drag and prop wash effects
				if (fabsf(local_pos.vz) < 2.f) {
					_hover_thrust_ekf.fuseAccZ(-local_pos.az, -local_pos_sp.thrust[2]);
				}

				const bool valid = (_hover_thrust_ekf.getHoverThrustEstimateVar() < 0.001f)
						   && (_hover_thrust_ekf.getInnovationTestRatio() < 1.f);
				_valid_hysteresis.set_state_and_update(valid, local_pos.timestamp);
				_valid = _valid_hysteresis.get_state();

				publishStatus(local_pos.timestamp);
			}
		}

	} else {
		_valid_hysteresis.set_state_and_update(false, hrt_absolute_time());

		if (!_armed) {
			reset();
		}

		if (_valid) {
			// only publish a single message to invalidate
			publishInvalidStatus();

			_valid = false;
		}
	}

	perf_end(_cycle_perf);
}

void MulticopterHoverThrustEstimator::publishStatus(const hrt_abstime &timestamp_sample)
{
	hover_thrust_estimate_s status_msg{};

	status_msg.timestamp_sample = timestamp_sample;

	status_msg.hover_thrust = _hover_thrust_ekf.getHoverThrustEstimate();
	status_msg.hover_thrust_var = _hover_thrust_ekf.getHoverThrustEstimateVar();

	status_msg.accel_innov = _hover_thrust_ekf.getInnovation();
	status_msg.accel_innov_var = _hover_thrust_ekf.getInnovationVar();
	status_msg.accel_innov_test_ratio = _hover_thrust_ekf.getInnovationTestRatio();
	status_msg.accel_noise_var = _hover_thrust_ekf.getAccelNoiseVar();

	status_msg.valid = _valid;

	status_msg.timestamp = hrt_absolute_time();

	_hover_thrust_ekf_pub.publish(status_msg);
}

void MulticopterHoverThrustEstimator::publishInvalidStatus()
{
	hover_thrust_estimate_s status_msg{};

	status_msg.hover_thrust = NAN;
	status_msg.hover_thrust_var = NAN;
	status_msg.accel_innov = NAN;
	status_msg.accel_innov_var = NAN;
	status_msg.accel_innov_test_ratio = NAN;
	status_msg.accel_noise_var = NAN;

	status_msg.timestamp = hrt_absolute_time();

	_hover_thrust_ekf_pub.publish(status_msg);
}

int MulticopterHoverThrustEstimator::task_spawn(int argc, char *argv[])
{
	MulticopterHoverThrustEstimator *instance = new MulticopterHoverThrustEstimator();

	if (instance) {
		_object.store(instance);
		_task_id = task_id_is_work_queue;

		if (instance->init()) {
			return PX4_OK;
		}

	} else {
		PX4_ERR("alloc failed");
	}

	delete instance;
	_object.store(nullptr);
	_task_id = -1;

	return PX4_ERROR;
}

int MulticopterHoverThrustEstimator::custom_command(int argc, char *argv[])
{
	return print_usage("unknown command");
}

int MulticopterHoverThrustEstimator::print_status()
{
	perf_print_counter(_cycle_perf);

	return 0;
}

int MulticopterHoverThrustEstimator::print_usage(const char *reason)
{
	if (reason) {
		PX4_WARN("%s\n", reason);
	}

	PRINT_MODULE_DESCRIPTION(
		R"DESCR_STR(
### Description

)DESCR_STR");

	PRINT_MODULE_USAGE_NAME("mc_hover_thrust_estimator", "estimator");
	PRINT_MODULE_USAGE_COMMAND("start");
	PRINT_MODULE_USAGE_DEFAULT_COMMANDS();

	return 0;
}

extern "C" __EXPORT int mc_hover_thrust_estimator_main(int argc, char *argv[])
{
	return MulticopterHoverThrustEstimator::main(argc, argv);
}
<commit_msg>HTE: relax validity condition when already valid (#17783)<commit_after>/****************************************************************************
 *
 *   Copyright (c) 2020 PX4 Development Team. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/**
 * @file hover_thrust_estimator.cpp
 *
 * @author Mathieu Bresciani 	<brescianimathieu@gmail.com>
 */

#include "MulticopterHoverThrustEstimator.hpp"

#include <mathlib/mathlib.h>

using namespace time_literals;

MulticopterHoverThrustEstimator::MulticopterHoverThrustEstimator() :
	ModuleParams(nullptr),
	WorkItem(MODULE_NAME, px4::wq_configurations::nav_and_controllers)
{
	_valid_hysteresis.set_hysteresis_time_from(false, 2_s);
	updateParams();
	reset();
}

MulticopterHoverThrustEstimator::~MulticopterHoverThrustEstimator()
{
	perf_free(_cycle_perf);
}

bool MulticopterHoverThrustEstimator::init()
{
	if (!_vehicle_local_position_sub.registerCallback()) {
		PX4_ERR("vehicle_local_position_setpoint callback registration failed!");
		return false;
	}

	return true;
}

void MulticopterHoverThrustEstimator::reset()
{
	_hover_thrust_ekf.setHoverThrust(_param_mpc_thr_hover.get());
	_hover_thrust_ekf.setHoverThrustStdDev(_param_hte_ht_err_init.get());
	_hover_thrust_ekf.resetAccelNoise();
}

void MulticopterHoverThrustEstimator::updateParams()
{
	const float ht_err_init_prev = _param_hte_ht_err_init.get();
	ModuleParams::updateParams();

	_hover_thrust_ekf.setProcessNoiseStdDev(_param_hte_ht_noise.get());

	if (fabsf(_param_hte_ht_err_init.get() - ht_err_init_prev) > FLT_EPSILON) {
		_hover_thrust_ekf.setHoverThrustStdDev(_param_hte_ht_err_init.get());
	}

	_hover_thrust_ekf.setAccelInnovGate(_param_hte_acc_gate.get());
}

void MulticopterHoverThrustEstimator::Run()
{
	if (should_exit()) {
		_vehicle_local_position_sub.unregisterCallback();
		exit_and_cleanup();
		return;
	}

	if (_vehicle_land_detected_sub.updated()) {
		vehicle_land_detected_s vehicle_land_detected;

		if (_vehicle_land_detected_sub.copy(&vehicle_land_detected)) {
			_landed = vehicle_land_detected.landed;

			if (_landed) {
				_in_air = false;
			}
		}
	}

	if (!_vehicle_local_position_sub.updated()) {
		return;
	}

	vehicle_local_position_s local_pos{};

	if (_vehicle_local_position_sub.copy(&local_pos)) {
		// This is only necessary because the landed
		// flag of the land detector does not guarantee that
		// the vehicle does not touch the ground anymore.
		// There is no check for the dist_bottom validity as
		// this value is always good enough after takeoff for
		// this use case.
		// TODO: improve the landed flag
		if (!_landed) {
			if (local_pos.dist_bottom > 1.f) {
				_in_air = true;
			}
		}
	}

	// new local position setpoint needed every iteration
	if (!_vehicle_local_position_setpoint_sub.updated()) {
		return;
	}

	// check for parameter updates
	if (_parameter_update_sub.updated()) {
		// clear update
		parameter_update_s pupdate;
		_parameter_update_sub.copy(&pupdate);

		// update parameters from storage
		updateParams();
	}

	perf_begin(_cycle_perf);

	if (_vehicle_status_sub.updated()) {
		vehicle_status_s vehicle_status;

		if (_vehicle_status_sub.copy(&vehicle_status)) {
			_armed = (vehicle_status.arming_state == vehicle_status_s::ARMING_STATE_ARMED);
		}
	}

	const float dt = (local_pos.timestamp - _timestamp_last) * 1e-6f;
	_timestamp_last = local_pos.timestamp;

	if (_armed && _in_air && (dt > 0.001f) && (dt < 1.f) && PX4_ISFINITE(local_pos.az)) {

		_hover_thrust_ekf.predict(dt);

		vehicle_local_position_setpoint_s local_pos_sp;

		if (_vehicle_local_position_setpoint_sub.copy(&local_pos_sp)) {
			if (PX4_ISFINITE(local_pos_sp.thrust[2])) {
				// Inform the hover thrust estimator about the measured vertical
				// acceleration (positive acceleration is up) and the current thrust (positive thrust is up)
				// Guard against fast up and down motions biasing the estimator due to large drag and prop wash effects
				if (fabsf(local_pos.vz) < 2.f) {
					_hover_thrust_ekf.fuseAccZ(-local_pos.az, -local_pos_sp.thrust[2]);
				}

				bool valid = (_hover_thrust_ekf.getHoverThrustEstimateVar() < 0.001f);

				// The test ratio does not need to pass all the time to have a valid estimate
				if (!_valid) {
					valid = valid && (_hover_thrust_ekf.getInnovationTestRatio() < 1.f);
				}

				_valid_hysteresis.set_state_and_update(valid, local_pos.timestamp);
				_valid = _valid_hysteresis.get_state();

				publishStatus(local_pos.timestamp);
			}
		}

	} else {
		_valid_hysteresis.set_state_and_update(false, hrt_absolute_time());

		if (!_armed) {
			reset();
		}

		if (_valid) {
			// only publish a single message to invalidate
			publishInvalidStatus();

			_valid = false;
		}
	}

	perf_end(_cycle_perf);
}

void MulticopterHoverThrustEstimator::publishStatus(const hrt_abstime &timestamp_sample)
{
	hover_thrust_estimate_s status_msg{};

	status_msg.timestamp_sample = timestamp_sample;

	status_msg.hover_thrust = _hover_thrust_ekf.getHoverThrustEstimate();
	status_msg.hover_thrust_var = _hover_thrust_ekf.getHoverThrustEstimateVar();

	status_msg.accel_innov = _hover_thrust_ekf.getInnovation();
	status_msg.accel_innov_var = _hover_thrust_ekf.getInnovationVar();
	status_msg.accel_innov_test_ratio = _hover_thrust_ekf.getInnovationTestRatio();
	status_msg.accel_noise_var = _hover_thrust_ekf.getAccelNoiseVar();

	status_msg.valid = _valid;

	status_msg.timestamp = hrt_absolute_time();

	_hover_thrust_ekf_pub.publish(status_msg);
}

void MulticopterHoverThrustEstimator::publishInvalidStatus()
{
	hover_thrust_estimate_s status_msg{};

	status_msg.hover_thrust = NAN;
	status_msg.hover_thrust_var = NAN;
	status_msg.accel_innov = NAN;
	status_msg.accel_innov_var = NAN;
	status_msg.accel_innov_test_ratio = NAN;
	status_msg.accel_noise_var = NAN;

	status_msg.timestamp = hrt_absolute_time();

	_hover_thrust_ekf_pub.publish(status_msg);
}

int MulticopterHoverThrustEstimator::task_spawn(int argc, char *argv[])
{
	MulticopterHoverThrustEstimator *instance = new MulticopterHoverThrustEstimator();

	if (instance) {
		_object.store(instance);
		_task_id = task_id_is_work_queue;

		if (instance->init()) {
			return PX4_OK;
		}

	} else {
		PX4_ERR("alloc failed");
	}

	delete instance;
	_object.store(nullptr);
	_task_id = -1;

	return PX4_ERROR;
}

int MulticopterHoverThrustEstimator::custom_command(int argc, char *argv[])
{
	return print_usage("unknown command");
}

int MulticopterHoverThrustEstimator::print_status()
{
	perf_print_counter(_cycle_perf);

	return 0;
}

int MulticopterHoverThrustEstimator::print_usage(const char *reason)
{
	if (reason) {
		PX4_WARN("%s\n", reason);
	}

	PRINT_MODULE_DESCRIPTION(
		R"DESCR_STR(
### Description

)DESCR_STR");

	PRINT_MODULE_USAGE_NAME("mc_hover_thrust_estimator", "estimator");
	PRINT_MODULE_USAGE_COMMAND("start");
	PRINT_MODULE_USAGE_DEFAULT_COMMANDS();

	return 0;
}

extern "C" __EXPORT int mc_hover_thrust_estimator_main(int argc, char *argv[])
{
	return MulticopterHoverThrustEstimator::main(argc, argv);
}
<|endoftext|>