<commit_before>// Copyright 2015-2019 Elviss Strazdins. All rights reserved.

#ifndef OUZEL_STORAGE_FILE_HPP
#define OUZEL_STORAGE_FILE_HPP

#include <string>
#include <system_error>
#include <vector>

#if defined(_WIN32)
#  pragma push_macro("WIN32_LEAN_AND_MEAN")
#  pragma push_macro("NOMINMAX")
#  ifndef WIN32_LEAN_AND_MEAN
#    define WIN32_LEAN_AND_MEAN
#  endif
#  ifndef NOMINMAX
#    define NOMINMAX
#  endif
#  include <Windows.h>
#  pragma pop_macro("WIN32_LEAN_AND_MEAN")
#  pragma pop_macro("NOMINMAX")
#else
#  include <fcntl.h>
#  include <sys/stat.h>
#  include <unistd.h>
#endif

#if defined(__ANDROID__)
#  include <android/asset_manager_jni.h>
#endif

namespace ouzel
{
    namespace storage
    {
        class File final
        {
        public:
#ifdef _WIN32
            using Type = HANDLE;
            static constexpr Type INVALID = INVALID_HANDLE_VALUE;
#else
            using Type = int;
            static constexpr Type INVALID = -1;
#endif

            enum Mode
            {
                Read = 0x01,
                Write = 0x02,
                Create = 0x04,
                Append = 0x08,
                Truncate = 0x10
            };

            enum class Seek
            {
                Begin,
                Current,
                End
            };

            File() noexcept = default;
            File(const std::string& filename, const int mode)
            {
#if defined(_WIN32)
                const int bufferSize = MultiByteToWideChar(CP_UTF8, 0, filename.c_str(), -1, nullptr, 0);
                if (bufferSize == 0)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to convert UTF-8 to wide char");

                std::vector<WCHAR> buffer(bufferSize);
                if (MultiByteToWideChar(CP_UTF8, 0, filename.c_str(), -1, buffer.data(), bufferSize) == 0)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to convert the filename to wide char");

                // relative paths longer than MAX_PATH are not supported
                if (buffer.size() > MAX_PATH)
                    buffer.insert(buffer.begin(), {L'\\', L'\\', L'?', L'\\'});

                const DWORD access =
                    ((mode & Mode::Read) ? GENERIC_READ : 0) |
                    ((mode & Mode::Write) ? GENERIC_WRITE : 0) |
                    ((mode & Mode::Append) ? FILE_APPEND_DATA : 0);

                const DWORD createDisposition = (mode & Truncate) ?
                    ((mode & Mode::Create) ? CREATE_ALWAYS : TRUNCATE_EXISTING) :
                    ((mode & Mode::Create) ? OPEN_ALWAYS : OPEN_EXISTING);

                file = CreateFileW(buffer.data(), access, 0, nullptr, createDisposition, FILE_ATTRIBUTE_NORMAL, nullptr);
                if (file == INVALID_HANDLE_VALUE)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to open file");
#else
                const int access =
                    ((mode & Mode::Read) && (mode & Mode::Write) ? O_RDWR :
                     (mode & Mode::Read) ? O_RDONLY :
                     (mode & Mode::Write) ? O_WRONLY : 0) |
                    ((mode & Mode::Create) ? O_CREAT : 0) |
                    ((mode & Mode::Append) ? O_APPEND : 0) |
                    ((mode & Mode::Truncate) ? O_TRUNC : 0);

                file = open(filename.c_str(), access, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
                if (file == -1)
                    throw std::system_error(errno, std::system_category(), "Failed to open file");
#endif
            }

            ~File()
            {
                if (file != INVALID)
#if defined(_WIN32)
                    CloseHandle(file);
#else
                    ::close(file);
#endif
            }

            File(const File&) = delete;
            File& operator=(const File&) = delete;

            File(File&& other) noexcept:
                file(other.file)
            {
                other.file = INVALID;
            }

            File& operator=(File&& other) noexcept
            {
                if (&other == this) return *this;

                if (file != INVALID)
#if defined(_WIN32)
                    CloseHandle(file);
#else
                    ::close(file);
#endif
                file = other.file;
                other.file = INVALID;

                return *this;
            }

            inline operator Type() const noexcept { return file; }

            inline auto isOpen() const noexcept
            {
                return file != INVALID;
            }

            void close()
            {
                if (file != INVALID)
#if defined(_WIN32)
                    if (!CloseHandle(file))
                        throw std::system_error(GetLastError(), std::system_category(), "Failed to close file");
#else
                    if (::close(file) == -1)
                        throw std::system_error(errno, std::system_category(), "Failed to close file");
#endif

                file = INVALID;
            }

            uint32_t read(void* buffer, uint32_t size, bool all = false) const
            {
                if (all)
                {
                    uint8_t* dest = static_cast<uint8_t*>(buffer);
                    uint32_t remaining = size;

                    while (remaining > 0)
                    {
                        const uint32_t bytesRead = read(dest, remaining);

                        if (bytesRead == 0)
                            return 0; // End of file reached

                        remaining -= bytesRead;
                        dest += bytesRead;
                    }

                    return size;
                }
                else
                {
                    if (file == INVALID)
                        throw std::runtime_error("File is not open");

#if defined(_WIN32)
                    DWORD n;
                    if (!ReadFile(file, buffer, size, &n, nullptr))
                        throw std::system_error(GetLastError(), std::system_category(), "Failed to read from file");

                    return static_cast<uint32_t>(n);
#else
                    const ssize_t ret = ::read(file, buffer, size);

                    if (ret == -1)
                        throw std::system_error(errno, std::system_category(), "Failed to read from file");

                    return static_cast<uint32_t>(ret);
#endif
                }
            }

            uint32_t write(const void* buffer, uint32_t size, bool all = false) const
            {
                if (all)
                {
                    const uint8_t* src = static_cast<const uint8_t*>(buffer);
                    uint32_t remaining = size;

                    while (remaining > 0)
                    {
                        const uint32_t bytesWritten = write(src, remaining, false);
                        remaining -= bytesWritten;
                        src += bytesWritten;
                    }

                    return size;
                }
                else
                {
                    if (file == INVALID)
                        throw std::runtime_error("File is not open");

#if defined(_WIN32)
                    DWORD n;
                    if (!WriteFile(file, buffer, size, &n, nullptr))
                        throw std::system_error(GetLastError(), std::system_category(), "Failed to write to file");

                    return static_cast<uint32_t>(n);
#else
                    const ssize_t ret = ::write(file, buffer, size);

                    if (ret == -1)
                        throw std::system_error(errno, std::system_category(), "Failed to write to file");

                    return static_cast<uint32_t>(ret);
#endif
                }
            }

            void seek(const int32_t offset, const Seek method) const
            {
                if (file == INVALID)
                    throw std::runtime_error("File is not open");

#if defined(_WIN32)
                const DWORD moveMethod =
                    (method == Seek::Begin) ? FILE_BEGIN :
                    (method == Seek::Current) ? FILE_CURRENT :
                    (method == Seek::End) ? FILE_END :
                    throw std::runtime_error("Unsupported seek method");

                if (SetFilePointer(file, offset, nullptr, moveMethod) == INVALID_SET_FILE_POINTER)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to seek file");
#else
                const int whence =
                    (method == Seek::Begin) ? SEEK_SET :
                    (method == Seek::Current) ? SEEK_CUR :
                    (method == Seek::End) ? SEEK_END :
                    throw std::runtime_error("Unsupported seek method");

                if (lseek(file, offset, whence) == -1)
                    throw std::system_error(errno, std::system_category(), "Failed to seek file");
#endif
            }

            auto getOffset() const
            {
                if (file == INVALID)
                    throw std::runtime_error("File is not open");

#if defined(_WIN32)
                DWORD ret = SetFilePointer(file, 0, nullptr, FILE_CURRENT);
                if (ret == INVALID_SET_FILE_POINTER)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to seek file");
                return static_cast<uint32_t>(ret);
#else
                off_t ret = lseek(file, 0, SEEK_CUR);
                if (ret == -1)
                    throw std::system_error(errno, std::system_category(), "Failed to seek file");
                return static_cast<uint32_t>(ret);
#endif
            }

        private:
            Type file = INVALID;
        };
    } // namespace storage
} // namespace ouzel

#endif // OUZEL_STORAGE_FILE_HPP
<commit_msg>Implement file copy constructor and assignment operator<commit_after>// Copyright 2015-2019 Elviss Strazdins. All rights reserved.

#ifndef OUZEL_STORAGE_FILE_HPP
#define OUZEL_STORAGE_FILE_HPP

#include <string>
#include <system_error>
#include <vector>

#if defined(_WIN32)
#  pragma push_macro("WIN32_LEAN_AND_MEAN")
#  pragma push_macro("NOMINMAX")
#  ifndef WIN32_LEAN_AND_MEAN
#    define WIN32_LEAN_AND_MEAN
#  endif
#  ifndef NOMINMAX
#    define NOMINMAX
#  endif
#  include <Windows.h>
#  pragma pop_macro("WIN32_LEAN_AND_MEAN")
#  pragma pop_macro("NOMINMAX")
#else
#  include <fcntl.h>
#  include <sys/stat.h>
#  include <unistd.h>
#endif

#if defined(__ANDROID__)
#  include <android/asset_manager_jni.h>
#endif

namespace ouzel
{
    namespace storage
    {
        class File final
        {
        public:
#ifdef _WIN32
            using Type = HANDLE;
            static constexpr Type INVALID = INVALID_HANDLE_VALUE;
#else
            using Type = int;
            static constexpr Type INVALID = -1;
#endif

            enum Mode
            {
                Read = 0x01,
                Write = 0x02,
                Create = 0x04,
                Append = 0x08,
                Truncate = 0x10
            };

            enum class Seek
            {
                Begin,
                Current,
                End
            };

            File() noexcept = default;
            File(const std::string& filename, const int mode)
            {
#if defined(_WIN32)
                const int bufferSize = MultiByteToWideChar(CP_UTF8, 0, filename.c_str(), -1, nullptr, 0);
                if (bufferSize == 0)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to convert UTF-8 to wide char");

                std::vector<WCHAR> buffer(bufferSize);
                if (MultiByteToWideChar(CP_UTF8, 0, filename.c_str(), -1, buffer.data(), bufferSize) == 0)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to convert the filename to wide char");

                // relative paths longer than MAX_PATH are not supported
                if (buffer.size() > MAX_PATH)
                    buffer.insert(buffer.begin(), {L'\\', L'\\', L'?', L'\\'});

                const DWORD access =
                    ((mode & Mode::Read) ? GENERIC_READ : 0) |
                    ((mode & Mode::Write) ? GENERIC_WRITE : 0) |
                    ((mode & Mode::Append) ? FILE_APPEND_DATA : 0);

                const DWORD createDisposition = (mode & Truncate) ?
                    ((mode & Mode::Create) ? CREATE_ALWAYS : TRUNCATE_EXISTING) :
                    ((mode & Mode::Create) ? OPEN_ALWAYS : OPEN_EXISTING);

                file = CreateFileW(buffer.data(), access, 0, nullptr, createDisposition, FILE_ATTRIBUTE_NORMAL, nullptr);
                if (file == INVALID_HANDLE_VALUE)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to open file");
#else
                const int access =
                    ((mode & Mode::Read) && (mode & Mode::Write) ? O_RDWR :
                     (mode & Mode::Read) ? O_RDONLY :
                     (mode & Mode::Write) ? O_WRONLY : 0) |
                    ((mode & Mode::Create) ? O_CREAT : 0) |
                    ((mode & Mode::Append) ? O_APPEND : 0) |
                    ((mode & Mode::Truncate) ? O_TRUNC : 0);

                file = open(filename.c_str(), access, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
                if (file == -1)
                    throw std::system_error(errno, std::system_category(), "Failed to open file");
#endif
            }

            ~File()
            {
                if (file != INVALID)
#if defined(_WIN32)
                    CloseHandle(file);
#else
                    ::close(file);
#endif
            }

            File(const File& other)
#if !defined(_WIN32)
                : file(dup(other.file))
#endif
            {
#if defined(_WIN32)
                if (!DuplicateHandle(GetCurrentProcess(),
                                     other.file,
                                     GetCurrentProcess(),
                                     &file,
                                     0,
                                     FALSE,
                                     DUPLICATE_SAME_ACCESS))
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to duplicate file handle");
#else
                if (file == -1)
                    throw std::system_error(errno, std::system_category(), "Failed to duplicate file descriptor");
#endif
            }

            File& operator=(const File& other)
            {
#if defined(_WIN32)
                if (file != INVALID_HANDLE_VALUE) CloseHandle(file);

                if (!DuplicateHandle(GetCurrentProcess(),
                                     other.file,
                                     GetCurrentProcess(),
                                     &file,
                                     0,
                                     FALSE,
                                     DUPLICATE_SAME_ACCESS))
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to duplicate file handle");
#else
                if (file != -1) ::close(file);

                file = dup(other.file);
                if (file == -1)
                    throw std::system_error(errno, std::system_category(), "Failed to duplicate file descriptor");
#endif

                return *this;
            }

            File(File&& other) noexcept:
                file(other.file)
            {
                other.file = INVALID;
            }

            File& operator=(File&& other) noexcept
            {
                if (&other == this) return *this;

                if (file != INVALID)
#if defined(_WIN32)
                    CloseHandle(file);
#else
                    ::close(file);
#endif
                file = other.file;
                other.file = INVALID;

                return *this;
            }

            inline operator Type() const noexcept { return file; }

            inline auto isOpen() const noexcept
            {
                return file != INVALID;
            }

            void close()
            {
                if (file != INVALID)
#if defined(_WIN32)
                    if (!CloseHandle(file))
                        throw std::system_error(GetLastError(), std::system_category(), "Failed to close file");
#else
                    if (::close(file) == -1)
                        throw std::system_error(errno, std::system_category(), "Failed to close file");
#endif

                file = INVALID;
            }

            uint32_t read(void* buffer, uint32_t size, bool all = false) const
            {
                if (all)
                {
                    uint8_t* dest = static_cast<uint8_t*>(buffer);
                    uint32_t remaining = size;

                    while (remaining > 0)
                    {
                        const uint32_t bytesRead = read(dest, remaining);

                        if (bytesRead == 0)
                            return 0; // End of file reached

                        remaining -= bytesRead;
                        dest += bytesRead;
                    }

                    return size;
                }
                else
                {
                    if (file == INVALID)
                        throw std::runtime_error("File is not open");

#if defined(_WIN32)
                    DWORD n;
                    if (!ReadFile(file, buffer, size, &n, nullptr))
                        throw std::system_error(GetLastError(), std::system_category(), "Failed to read from file");

                    return static_cast<uint32_t>(n);
#else
                    const ssize_t ret = ::read(file, buffer, size);

                    if (ret == -1)
                        throw std::system_error(errno, std::system_category(), "Failed to read from file");

                    return static_cast<uint32_t>(ret);
#endif
                }
            }

            uint32_t write(const void* buffer, uint32_t size, bool all = false) const
            {
                if (all)
                {
                    const uint8_t* src = static_cast<const uint8_t*>(buffer);
                    uint32_t remaining = size;

                    while (remaining > 0)
                    {
                        const uint32_t bytesWritten = write(src, remaining, false);
                        remaining -= bytesWritten;
                        src += bytesWritten;
                    }

                    return size;
                }
                else
                {
                    if (file == INVALID)
                        throw std::runtime_error("File is not open");

#if defined(_WIN32)
                    DWORD n;
                    if (!WriteFile(file, buffer, size, &n, nullptr))
                        throw std::system_error(GetLastError(), std::system_category(), "Failed to write to file");

                    return static_cast<uint32_t>(n);
#else
                    const ssize_t ret = ::write(file, buffer, size);

                    if (ret == -1)
                        throw std::system_error(errno, std::system_category(), "Failed to write to file");

                    return static_cast<uint32_t>(ret);
#endif
                }
            }

            void seek(const int32_t offset, const Seek method) const
            {
                if (file == INVALID)
                    throw std::runtime_error("File is not open");

#if defined(_WIN32)
                const DWORD moveMethod =
                    (method == Seek::Begin) ? FILE_BEGIN :
                    (method == Seek::Current) ? FILE_CURRENT :
                    (method == Seek::End) ? FILE_END :
                    throw std::runtime_error("Unsupported seek method");

                if (SetFilePointer(file, offset, nullptr, moveMethod) == INVALID_SET_FILE_POINTER)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to seek file");
#else
                const int whence =
                    (method == Seek::Begin) ? SEEK_SET :
                    (method == Seek::Current) ? SEEK_CUR :
                    (method == Seek::End) ? SEEK_END :
                    throw std::runtime_error("Unsupported seek method");

                if (lseek(file, offset, whence) == -1)
                    throw std::system_error(errno, std::system_category(), "Failed to seek file");
#endif
            }

            auto getOffset() const
            {
                if (file == INVALID)
                    throw std::runtime_error("File is not open");

#if defined(_WIN32)
                DWORD ret = SetFilePointer(file, 0, nullptr, FILE_CURRENT);
                if (ret == INVALID_SET_FILE_POINTER)
                    throw std::system_error(GetLastError(), std::system_category(), "Failed to seek file");
                return static_cast<uint32_t>(ret);
#else
                off_t ret = lseek(file, 0, SEEK_CUR);
                if (ret == -1)
                    throw std::system_error(errno, std::system_category(), "Failed to seek file");
                return static_cast<uint32_t>(ret);
#endif
            }

        private:
            Type file = INVALID;
        };
    } // namespace storage
} // namespace ouzel

#endif // OUZEL_STORAGE_FILE_HPP
<|endoftext|>


<commit_before>#ifndef _SDD_MEM_CACHE_HH_
#define _SDD_MEM_CACHE_HH_

#include <algorithm> // for_each, nth_element
#include <cstdint>   // uint32_t
#include <forward_list>
#include <iterator>  // distance
#include <numeric>   // accumulate
#include <tuple>
#include <utility>   // forward
#include <vector>

#include "sdd/mem/hash_table.hh"
#include "sdd/mem/interrupt.hh"
#include "sdd/util/hash.hh"
#include "sdd/util/packed.hh"

namespace sdd { namespace mem {

/*------------------------------------------------------------------------------------------------*/

/// @internal
/// @brief Used by cache to know if an operation should be cached or not.
///
/// A filter should always return the same result for the same operation.
template <typename T, typename... Filters>
struct apply_filters;

/// @internal
/// @brief Base case.
///
/// All filters have been applied and they all accepted the operation.
template <typename T>
struct apply_filters<T>
{
  constexpr
  bool
  operator()(const T&)
  const
  {
    return true;
  }
};

/// @internal
/// @brief Recursive case.
///
/// Chain filters calls: as soon as a filter reject an operation, the evaluation is stopped.
template <typename T, typename Filter, typename... Filters>
struct apply_filters<T, Filter, Filters...>
{
  bool
  operator()(const T& op)
  const
  {
    return Filter()(op) ? apply_filters<T, Filters...>()(op) : false;
  }
};

/*------------------------------------------------------------------------------------------------*/

namespace /* anonymous */ {

/// @internal
/// @brief The statistics of a cache.
///
/// A statistic is made of several rounds: each time a cache is cleaned up, a new round is
/// created. Thus, one can have detailed statistics to see how well the cache performed.
struct cache_statistics
{
  /// @internal
  /// @brief Statistic between two cleanups.
  struct round
  {
    round()
      : hits(0)
      , misses(0)
      , filtered(0)
    {}

    /// @brief The number of hits in a round.
    std::size_t hits;

    /// @brief The number of misses in a round.
    std::size_t misses;

    /// @brief The number of filtered entries in a round.
    std::size_t filtered;
  };

  /// @brief The list of all rounds.
  std::forward_list<round> rounds;

  /// @brief Default constructor.
  cache_statistics()
    : rounds(1)
  {}

  /// @brief Get the number of rounds.
  std::size_t
  size()
  const noexcept
  {
    return std::distance(rounds.begin(), rounds.end()); // forward_list lacks a size() function
  }

  /// @brief Get the number of performed cleanups.
  std::size_t
  cleanups()
  const noexcept
  {
    return size() - 1;
  }

  round
  total()
  const noexcept
  {
    return std::accumulate( rounds.begin(), rounds.end(), round()
                          , [&](const round& acc, const round& r) -> round
                               {
                                 round res;
                                 res.hits = acc.hits + r.hits;
                                 res.misses = acc.misses + r.misses;
                                 res.filtered = acc.filtered + r.filtered;
                                 return res;
                               }
                          );
  }
};

} // namespace anonymous

/*------------------------------------------------------------------------------------------------*/

/// @internal
/// @brief  A generic cache.
/// @tparam Operation is the operation type.
/// @tparam EvaluationError is the exception that the evaluation of an Operation can throw.
/// @tparam Filters is a list of filters that reject some operations.
///
/// It uses the LRU strategy to cleanup old entries.
template < typename Context, typename Operation, typename EvaluationError
         , typename... Filters>
class cache
{
  // Can't copy a cache.
  cache(const cache&) = delete;
  cache* operator=(const cache&) = delete;

private:

  /// @brief The type of the context of this cache.
  using context_type = Context;

  /// @brief The type of the result of an operation stored in the cache.
  using result_type = typename Operation::result_type;

  /// @brief Associate an operation to its result into the cache.
  ///
  /// The operation acts as a key and the associated result is the value counterpart.
  struct
#if defined __clang__
  LIBSDD_ATTRIBUTE_PACKED
#endif
  cache_entry
  {
    // Can't copy a cache_entry.
    cache_entry(const cache_entry&) = delete;
    cache_entry& operator=(const cache_entry&) = delete;

    /// @brief
    mem::intrusive_member_hook<cache_entry> hook;

    /// @brief The cached operation.
    const Operation operation;

    /// @brief The result of the evaluation of operation.
    const result_type result;

    /// @brief The last time this entry has been used.
    ///
    /// Used by the LRU cache cleanup strategy.
    std::uint32_t date_;

    /// brief The 'in use' bit position in nb_hits_.
    static constexpr std::uint32_t in_use_mask = (1u << 31);

    /// @brief Constructor.
    template <typename... Args>
    cache_entry(Operation&& op, Args&&... args)
      : hook()
      , operation(std::move(op))
      , result(std::forward<Args>(args)...)
      , date_(in_use_mask) // initially in use
    {}

    /// @brief Cache entries are only compared using their operations.
    bool
    operator==(const cache_entry& other)
    const noexcept
    {
      return operation == other.operation;
    }

    /// @brief Get the number of hits, with the 'in use' bit possibly set.
    unsigned int
    raw_date()
    const noexcept
    {
      return date_;
    }

    /// @brief Set this cache entry to a 'never accessed' state.
    void
    reset_date()
    noexcept
    {
      date_ &= in_use_mask;
    }

    /// @brief Increment the number of hits.
    void
    set_date(std::uint32_t last_date)
    noexcept
    {
      date_ = last_date | (date_ & in_use_mask);
    }

    /// @brief Set this cache entry to a 'not in use' state.
    void
    reset_in_use()
    noexcept
    {
      date_ &= ~in_use_mask;
    }

    /// @brief Tell if this cache entry is in an 'in use' state.
    bool
    in_use()
    const noexcept
    {
      return date_ & in_use_mask;
    }
  };

  /// @brief Hash a cache_entry.
  ///
  /// We only use the Operation part of a cache_entry to find it in the cache, in order
  /// to manage the set that stores cache entries as a map.
  struct hash_key
  {
    std::size_t
    operator()(const cache_entry& x)
    const noexcept(noexcept(util::hash(x.operation)))
    {
      return util::hash(x.operation);
    }
  };

  /// @brief This cache's context.
  context_type& cxt_;

  /// @brief The cache name.
  const std::string name_;

  /// @brief The wanted load factor.
  double max_load_factor_;

  /// @brief An intrusive hash table.
  using set_type = mem::hash_table<cache_entry, hash_key>;

  /// @brief The actual storage of caches entries.
  set_type set_;

  /// @brief The maximum size this cache is authorized to grow to.
  std::size_t max_size_;

  /// @brief The statistics of this cache.
  cache_statistics stats_;

  /// @brief The date of last access.
  std::uint32_t date_;

public:

  /// @brief Construct a cache.
  /// @param context This cache's context.
  /// @param name Give a name to this cache.
  /// @param size tells how many cache entries are keeped in the cache.
  ///
  /// When the maximal size is reached, a cleanup is launched: half of the cache is removed,
  /// using a LRU strategy. This cache will never perform a rehash, therefore it allocates
  /// all the memory it needs at its construction.
  cache(context_type& context, const std::string& name, std::size_t size)
    : cxt_(context)
    , name_(name)
    , max_load_factor_(0.9)
    , set_(size)
    , max_size_(set_.bucket_count())
    , stats_()
    , date_(0)
  {}

  /// @brief Destructor.
  ~cache()
  {
    clear();
  }

  /// @brief Cache lookup.
  result_type
  operator()(Operation&& op)
  {
    // Check if the current operation should not be cached.
    if (not apply_filters<Operation, Filters...>()(op))
    {
      ++stats_.rounds.front().filtered;
      try
      {
        return op(cxt_);
      }
      catch (EvaluationError& e)
      {
        --stats_.rounds.front().filtered;
        e.add_step(std::move(op));
        throw;
      }
    }

    // Lookup for op.
    typename set_type::insert_commit_data commit_data;
    auto insertion = set_.insert_check( op
                                      , std::hash<Operation>()
                                      , [](const Operation& lhs, const cache_entry& rhs)
                                          {return lhs == rhs.operation;}
                                      , commit_data);

    // Check if op has already been computed.
    if (not insertion.second)
    {
      ++stats_.rounds.front().hits;
      insertion.first->set_date(++date_);
      return insertion.first->result;
    }

    ++stats_.rounds.front().misses;

    // Clean up the cache, if necessary.
    cleanup();

    cache_entry* entry;
    try
    {
      entry = new cache_entry(std::move(op), op(cxt_));
    }
    catch (EvaluationError& e)
    {
      --stats_.rounds.front().misses;
      e.add_step(std::move(op));
      throw;
    }
    catch (interrupt<result_type>&)
    {
      --stats_.rounds.front().misses;
      throw;
    }
    // A cache entry is constructed with the 'in use' bit set.
    entry->reset_in_use();
    set_.insert_commit(*entry, commit_data); // doesn't throw
    return entry->result;
  }

  /// @brief The load factor of the underlying hash table.
  double
  load_factor()
  const noexcept
  {
    return static_cast<double>(set_.size()) / static_cast<double>(max_size_);
  }

  /// @brief Remove half of the cache (LRU strategy).
  void
  cleanup()
  {
    if (load_factor() < max_load_factor_)
    {
      return;
    }

    stats_.rounds.emplace_front(cache_statistics::round());

    std::vector<cache_entry*> vec;
    vec.reserve(set_.size());
    for (auto& e : set_)
    {
      vec.push_back(&e);
    }

    // Compute the number of elements to keep in order to reduce the load factor by a factor of 2.
    const std::size_t to_keep = static_cast<std::size_t>(max_size_ * max_load_factor_/2);
    const std::size_t cut_point = vec.size() - to_keep;

    // Find the median of the number of hits.
    std::nth_element( vec.begin(), vec.begin() + cut_point, vec.end()
                    , [](cache_entry* lhs, cache_entry* rhs)
                        {
                          // We sort using the raw_date counter, with the 'in use' bit possibly
                          // set. As this is the highest bit, the value of date may be large.
                          // Thus, it's very unlikely that cache entries currently in use will be
                          // put below the median.
                          return lhs->raw_date() < rhs->raw_date();
                        });

    // Delete all cache entries with a number of entries smaller than the median.
    std::for_each( vec.begin(), vec.begin() + cut_point
                 , [&](cache_entry* e)
                      {
                        if (not e->in_use())
                        {
                          const auto cit = set_.find(*e);
                          if (cit != set_.end())
                          {
                            set_.erase(cit);
                            delete e;
                          }
                        }
                      });

    // Reset the number of hits of all remaining cache entries.
    std::for_each(vec.begin() + cut_point, vec.end(), [](cache_entry* e){e->reset_date();});

    // Reset the global date.
    date_ = 0;
  }

  /// @brief Remove all entries of the cache.
  void
  clear()
  noexcept
  {
    set_.clear_and_dispose([](cache_entry* x){delete x;});
  }

  /// @brief Get the number of cached operations.
  std::size_t
  size()
  const noexcept
  {
    return set_.size();
  }

  /// @brief Get the statistics of this cache.
  const cache_statistics&
  statistics()
  const noexcept
  {
    return stats_;
  }

  /// @brief Get this cache's name
  const std::string&
  name()
  const noexcept
  {
    return name_;
  }
};

/*------------------------------------------------------------------------------------------------*/

}} // namespace sdd::mem

#endif // _SDD_MEM_CACHE_HH_
<commit_msg>Hybrid cache cleanup strategy.<commit_after>#ifndef _SDD_MEM_CACHE_HH_
#define _SDD_MEM_CACHE_HH_

#include <algorithm> // for_each, nth_element
#include <cstdint>   // uint32_t
#include <forward_list>
#include <iterator>  // distance
#include <numeric>   // accumulate
#include <tuple>
#include <utility>   // forward
#include <vector>

#include "sdd/mem/hash_table.hh"
#include "sdd/mem/interrupt.hh"
#include "sdd/util/hash.hh"
#include "sdd/util/packed.hh"

namespace sdd { namespace mem {

/*------------------------------------------------------------------------------------------------*/

/// @internal
/// @brief Used by cache to know if an operation should be cached or not.
///
/// A filter should always return the same result for the same operation.
template <typename T, typename... Filters>
struct apply_filters;

/// @internal
/// @brief Base case.
///
/// All filters have been applied and they all accepted the operation.
template <typename T>
struct apply_filters<T>
{
  constexpr
  bool
  operator()(const T&)
  const
  {
    return true;
  }
};

/// @internal
/// @brief Recursive case.
///
/// Chain filters calls: as soon as a filter reject an operation, the evaluation is stopped.
template <typename T, typename Filter, typename... Filters>
struct apply_filters<T, Filter, Filters...>
{
  bool
  operator()(const T& op)
  const
  {
    return Filter()(op) ? apply_filters<T, Filters...>()(op) : false;
  }
};

/*------------------------------------------------------------------------------------------------*/

namespace /* anonymous */ {

/// @internal
/// @brief The statistics of a cache.
///
/// A statistic is made of several rounds: each time a cache is cleaned up, a new round is
/// created. Thus, one can have detailed statistics to see how well the cache performed.
struct cache_statistics
{
  /// @internal
  /// @brief Statistic between two cleanups.
  struct round
  {
    round()
      : hits(0)
      , misses(0)
      , filtered(0)
    {}

    /// @brief The number of hits in a round.
    std::size_t hits;

    /// @brief The number of misses in a round.
    std::size_t misses;

    /// @brief The number of filtered entries in a round.
    std::size_t filtered;
  };

  /// @brief The list of all rounds.
  std::forward_list<round> rounds;

  /// @brief Default constructor.
  cache_statistics()
    : rounds(1)
  {}

  /// @brief Get the number of rounds.
  std::size_t
  size()
  const noexcept
  {
    return std::distance(rounds.begin(), rounds.end()); // forward_list lacks a size() function
  }

  /// @brief Get the number of performed cleanups.
  std::size_t
  cleanups()
  const noexcept
  {
    return size() - 1;
  }

  round
  total()
  const noexcept
  {
    return std::accumulate( rounds.begin(), rounds.end(), round()
                          , [&](const round& acc, const round& r) -> round
                               {
                                 round res;
                                 res.hits = acc.hits + r.hits;
                                 res.misses = acc.misses + r.misses;
                                 res.filtered = acc.filtered + r.filtered;
                                 return res;
                               }
                          );
  }
};

} // namespace anonymous

/*------------------------------------------------------------------------------------------------*/

/// @internal
/// @brief  A generic cache.
/// @tparam Operation is the operation type.
/// @tparam EvaluationError is the exception that the evaluation of an Operation can throw.
/// @tparam Filters is a list of filters that reject some operations.
///
/// It uses the LRU strategy to cleanup old entries.
template < typename Context, typename Operation, typename EvaluationError
         , typename... Filters>
class cache
{
  // Can't copy a cache.
  cache(const cache&) = delete;
  cache* operator=(const cache&) = delete;

private:

  /// @brief The type of the context of this cache.
  using context_type = Context;

  /// @brief The type of the result of an operation stored in the cache.
  using result_type = typename Operation::result_type;

  /// @brief Associate an operation to its result into the cache.
  ///
  /// The operation acts as a key and the associated result is the value counterpart.
  struct
#if defined __clang__
  LIBSDD_ATTRIBUTE_PACKED
#endif
  cache_entry
  {
    // Can't copy a cache_entry.
    cache_entry(const cache_entry&) = delete;
    cache_entry& operator=(const cache_entry&) = delete;

    /// @brief
    mem::intrusive_member_hook<cache_entry> hook;

    /// @brief The cached operation.
    const Operation operation;

    /// @brief The result of the evaluation of operation.
    const result_type result;

    /// @brief The last time this entry has been used.
    ///
    /// Used by the LRU cache cleanup strategy.
    std::uint32_t date_;

    std::uint32_t nb_hits_;

    /// brief The 'in use' bit position in nb_hits_.
    static constexpr std::uint32_t in_use_mask = (1u << 31);

    /// @brief Constructor.
    template <typename... Args>
    cache_entry(Operation&& op, Args&&... args)
      : hook()
      , operation(std::move(op))
      , result(std::forward<Args>(args)...)
      , date_(in_use_mask) // initially in use
      , nb_hits_(0)
    {}

    /// @brief Cache entries are only compared using their operations.
    bool
    operator==(const cache_entry& other)
    const noexcept
    {
      return operation == other.operation;
    }

    /// @brief Get the number of hits, with the 'in use' bit possibly set.
    std::uint32_t
    raw_date()
    const noexcept
    {
      return date_;
    }

    /// @brief Set this cache entry to a 'never accessed' state.
    void
    reset_date()
    noexcept
    {
      date_ &= in_use_mask;
    }

    /// @brief Increment the number of hits.
    void
    set_date(std::uint32_t last_date)
    noexcept
    {
      date_ = last_date | (date_ & in_use_mask);
    }

    /// @brief Set this cache entry to a 'not in use' state.
    void
    reset_in_use()
    noexcept
    {
      date_ &= ~in_use_mask;
    }

    /// @brief Tell if this cache entry is in an 'in use' state.
    bool
    in_use()
    const noexcept
    {
      return date_ & in_use_mask;
    }

    /// @brief Increment the number of hits.
    void
    increment_nb_hits()
    noexcept
    {
      ++nb_hits_;
    }

    /// @brief Get the number of hits.
    std::uint32_t
    nb_hits()
    const noexcept
    {
      return nb_hits_;
    }
  };

  /// @brief Hash a cache_entry.
  ///
  /// We only use the Operation part of a cache_entry to find it in the cache, in order
  /// to manage the set that stores cache entries as a map.
  struct hash_key
  {
    std::size_t
    operator()(const cache_entry& x)
    const noexcept(noexcept(util::hash(x.operation)))
    {
      return util::hash(x.operation);
    }
  };

  /// @brief This cache's context.
  context_type& cxt_;

  /// @brief The cache name.
  const std::string name_;

  /// @brief The wanted load factor.
  double max_load_factor_;

  /// @brief An intrusive hash table.
  using set_type = mem::hash_table<cache_entry, hash_key>;

  /// @brief The actual storage of caches entries.
  set_type set_;

  /// @brief The maximum size this cache is authorized to grow to.
  std::size_t max_size_;

  /// @brief The statistics of this cache.
  cache_statistics stats_;

  /// @brief The date of last access.
  std::uint32_t date_;

public:

  /// @brief Construct a cache.
  /// @param context This cache's context.
  /// @param name Give a name to this cache.
  /// @param size tells how many cache entries are keeped in the cache.
  ///
  /// When the maximal size is reached, a cleanup is launched: half of the cache is removed,
  /// using a LRU strategy. This cache will never perform a rehash, therefore it allocates
  /// all the memory it needs at its construction.
  cache(context_type& context, const std::string& name, std::size_t size)
    : cxt_(context)
    , name_(name)
    , max_load_factor_(0.9)
    , set_(size)
    , max_size_(set_.bucket_count())
    , stats_()
    , date_(0)
  {}

  /// @brief Destructor.
  ~cache()
  {
    clear();
  }

  /// @brief Cache lookup.
  result_type
  operator()(Operation&& op)
  {
    // Check if the current operation should not be cached.
    if (not apply_filters<Operation, Filters...>()(op))
    {
      ++stats_.rounds.front().filtered;
      try
      {
        return op(cxt_);
      }
      catch (EvaluationError& e)
      {
        --stats_.rounds.front().filtered;
        e.add_step(std::move(op));
        throw;
      }
    }

    // Lookup for op.
    typename set_type::insert_commit_data commit_data;
    auto insertion = set_.insert_check( op
                                      , std::hash<Operation>()
                                      , [](const Operation& lhs, const cache_entry& rhs)
                                          {return lhs == rhs.operation;}
                                      , commit_data);

    // Check if op has already been computed.
    if (not insertion.second)
    {
      ++stats_.rounds.front().hits;
      insertion.first->set_date(++date_);
      insertion.first->increment_nb_hits();
      return insertion.first->result;
    }

    ++stats_.rounds.front().misses;

    // Clean up the cache, if necessary.
    cleanup();

    cache_entry* entry;
    try
    {
      entry = new cache_entry(std::move(op), op(cxt_));
    }
    catch (EvaluationError& e)
    {
      --stats_.rounds.front().misses;
      e.add_step(std::move(op));
      throw;
    }
    catch (interrupt<result_type>&)
    {
      --stats_.rounds.front().misses;
      throw;
    }
    // A cache entry is constructed with the 'in use' bit set.
    entry->reset_in_use();
    set_.insert_commit(*entry, commit_data); // doesn't throw
    return entry->result;
  }

  /// @brief The load factor of the underlying hash table.
  double
  load_factor()
  const noexcept
  {
    return static_cast<double>(set_.size()) / static_cast<double>(max_size_);
  }

  /// @brief Remove half of the cache (LRU strategy).
  void
  cleanup()
  {
    if (load_factor() < max_load_factor_)
    {
      return;
    }

    stats_.rounds.emplace_front(cache_statistics::round());

    std::vector<cache_entry*> vec;
    vec.reserve(set_.size());
    for (auto& e : set_)
    {
      vec.push_back(&e);
    }

    // Compute the number of elements to keep in order to reduce the load factor by a factor of 2.
    const std::size_t to_keep = static_cast<std::size_t>(max_size_ * max_load_factor_/2);
    const std::size_t cut_point = vec.size() - to_keep;

    // Find the median of the number of hits.
    std::nth_element( vec.begin(), vec.begin() + cut_point, vec.end()
                    , [](cache_entry* lhs, cache_entry* rhs)
                        {
                          // We sort using the raw_date counter, with the 'in use' bit possibly
                          // set. As this is the highest bit, the value of date may be large.
                          // Thus, it's very unlikely that cache entries currently in use will be
                          // put below the median.
                          return lhs->raw_date() < rhs->raw_date();
                        });

    // Give a chance to frequently used elements, but not so recently, to live.
    std::sort( vec.begin() + cut_point - (cut_point/2), vec.begin() + cut_point + (cut_point/2)
             , [](cache_entry* lhs, cache_entry* rhs){return lhs->nb_hits() < rhs->nb_hits();});

    // Delete all cache entries with a number of entries smaller than the median.
    std::for_each( vec.begin(), vec.begin() + cut_point
                 , [&](cache_entry* e)
                      {
                        if (not e->in_use())
                        {
                          const auto cit = set_.find(*e);
                          if (cit != set_.end())
                          {
                            set_.erase(cit);
                            delete e;
                          }
                        }
                      });

    // Reset the number of hits of all remaining cache entries.
    std::for_each(vec.begin() + cut_point, vec.end(), [](cache_entry* e){e->reset_date();});

    // Reset the global date.
    date_ = 0;
  }

  /// @brief Remove all entries of the cache.
  void
  clear()
  noexcept
  {
    set_.clear_and_dispose([](cache_entry* x){delete x;});
  }

  /// @brief Get the number of cached operations.
  std::size_t
  size()
  const noexcept
  {
    return set_.size();
  }

  /// @brief Get the statistics of this cache.
  const cache_statistics&
  statistics()
  const noexcept
  {
    return stats_;
  }

  /// @brief Get this cache's name
  const std::string&
  name()
  const noexcept
  {
    return name_;
  }
};

/*------------------------------------------------------------------------------------------------*/

}} // namespace sdd::mem

#endif // _SDD_MEM_CACHE_HH_
<|endoftext|>


<commit_before>#ifndef _SDD_MEM_CACHE_HH_
#define _SDD_MEM_CACHE_HH_

#include <algorithm> // for_each, nth_element
#include <cstdint>   // uint32_t
#include <forward_list>
#include <iterator>  // distance
#include <list>
#include <numeric>   // accumulate
#include <tuple>
#include <utility>   // forward
#include <vector>

#include "sdd/mem/hash_table.hh"
#include "sdd/mem/interrupt.hh"
#include "sdd/util/hash.hh"
#include "sdd/util/packed.hh"

namespace sdd { namespace mem {

/*------------------------------------------------------------------------------------------------*/

/// @internal
/// @brief Used by cache to know if an operation should be cached or not.
///
/// A filter should always return the same result for the same operation.
template <typename T, typename... Filters>
struct apply_filters;

/// @internal
/// @brief Base case.
///
/// All filters have been applied and they all accepted the operation.
template <typename T>
struct apply_filters<T>
{
  constexpr
  bool
  operator()(const T&)
  const
  {
    return true;
  }
};

/// @internal
/// @brief Recursive case.
///
/// Chain filters calls: as soon as a filter reject an operation, the evaluation is stopped.
template <typename T, typename Filter, typename... Filters>
struct apply_filters<T, Filter, Filters...>
{
  bool
  operator()(const T& op)
  const
  {
    return Filter()(op) ? apply_filters<T, Filters...>()(op) : false;
  }
};

/*------------------------------------------------------------------------------------------------*/

namespace /* anonymous */ {

/// @internal
/// @brief The statistics of a cache.
///
/// A statistic is made of several rounds: each time a cache is cleaned up, a new round is
/// created. Thus, one can have detailed statistics to see how well the cache performed.
struct cache_statistics
{
  /// @internal
  /// @brief Statistic between two cleanups.
  struct round
  {
    round()
      : hits(0)
      , misses(0)
      , filtered(0)
    {}

    /// @brief The number of hits in a round.
    std::size_t hits;

    /// @brief The number of misses in a round.
    std::size_t misses;

    /// @brief The number of filtered entries in a round.
    std::size_t filtered;
  };

  /// @brief The list of all rounds.
  std::forward_list<round> rounds;

  /// @brief Default constructor.
  cache_statistics()
    : rounds(1)
  {}

  /// @brief Get the number of rounds.
  std::size_t
  size()
  const noexcept
  {
    return std::distance(rounds.begin(), rounds.end()); // forward_list lacks a size() function
  }

  /// @brief Get the number of performed cleanups.
  std::size_t
  cleanups()
  const noexcept
  {
    return size() - 1;
  }

  round
  total()
  const noexcept
  {
    return std::accumulate( rounds.begin(), rounds.end(), round()
                          , [&](const round& acc, const round& r) -> round
                               {
                                 round res;
                                 res.hits = acc.hits + r.hits;
                                 res.misses = acc.misses + r.misses;
                                 res.filtered = acc.filtered + r.filtered;
                                 return res;
                               }
                          );
  }
};

} // namespace anonymous

/*------------------------------------------------------------------------------------------------*/

/// @internal
/// @brief  A generic cache.
/// @tparam Operation is the operation type.
/// @tparam EvaluationError is the exception that the evaluation of an Operation can throw.
/// @tparam Filters is a list of filters that reject some operations.
///
/// It uses the LRU strategy to cleanup old entries.
template < typename Context, typename Operation, typename EvaluationError
         , typename... Filters>
class cache
{
  // Can't copy a cache.
  cache(const cache&) = delete;
  cache* operator=(const cache&) = delete;

private:

  /// @brief The type of the context of this cache.
  using context_type = Context;

  /// @brief The type of the result of an operation stored in the cache.
  using result_type = typename Operation::result_type;

  // Forward declaration.
  struct cache_entry;

  /// @brief The type of the container that sorts cache entries by last access date.
  using lru_list_type = std::list<cache_entry*>;

  /// @brief Associate an operation to its result into the cache.
  ///
  /// The operation acts as a key and the associated result is the value counterpart.
  struct
#if defined __clang__
  LIBSDD_ATTRIBUTE_PACKED
#endif
  cache_entry
  {
    // Can't copy a cache_entry.
    cache_entry(const cache_entry&) = delete;
    cache_entry& operator=(const cache_entry&) = delete;

    /// @brief
    mem::intrusive_member_hook<cache_entry> hook;

    /// @brief The cached operation.
    const Operation operation;

    /// @brief The result of the evaluation of operation.
    const result_type result;

    /// @brief Where this cache entry is stored in the LRU list.
    typename lru_list_type::const_iterator lru_cit_;

    /// @brief Constructor.
    template <typename... Args>
    cache_entry(Operation&& op, Args&&... args)
      : hook()
      , operation(std::move(op))
      , result(std::forward<Args>(args)...)
      , lru_cit_()
    {}

    /// @brief Cache entries are only compared using their operations.
    bool
    operator==(const cache_entry& other)
    const noexcept
    {
      return operation == other.operation;
    }
  };

  /// @brief Hash a cache_entry.
  ///
  /// We only use the Operation part of a cache_entry to find it in the cache, in order
  /// to manage the set that stores cache entries as a map.
  struct hash_key
  {
    std::size_t
    operator()(const cache_entry& x)
    const noexcept(noexcept(util::hash(x.operation)))
    {
      return util::hash(x.operation);
    }
  };

  /// @brief An intrusive hash table.
  using set_type = mem::hash_table<cache_entry, hash_key>;

  /// @brief This cache's context.
  context_type& cxt_;

  /// @brief The cache name.
  const std::string name_;

  /// @brief The wanted load factor for the underlying hash table.
  static constexpr double max_load_factor = 0.85;

  /// @brief The actual storage of caches entries.
  set_type set_;

  /// @brief The the container that sorts cache entries by last access date.
  lru_list_type lru_list_;

  /// @brief The maximum size this cache is authorized to grow to.
  std::size_t max_size_;

  /// @brief The statistics of this cache.
  cache_statistics stats_;

public:

  /// @brief Construct a cache.
  /// @param context This cache's context.
  /// @param name Give a name to this cache.
  /// @param size tells how many cache entries are keeped in the cache.
  ///
  /// When the maximal size is reached, a cleanup is launched: half of the cache is removed,
  /// using a LRU strategy. This cache will never perform a rehash, therefore it allocates
  /// all the memory it needs at its construction.
  cache(context_type& context, const std::string& name, std::size_t size)
    : cxt_(context)
    , name_(name)
    , set_(size, max_load_factor, true /* no rehash */)
    , lru_list_()
    , max_size_(set_.bucket_count() * max_load_factor)
    , stats_()
  {}

  /// @brief Destructor.
  ~cache()
  {
    clear();
  }

  /// @brief Cache lookup.
  result_type
  operator()(Operation&& op)
  {
    // Check if the current operation should not be cached.
    if (not apply_filters<Operation, Filters...>()(op))
    {
      ++stats_.rounds.front().filtered;
      try
      {
        return op(cxt_);
      }
      catch (EvaluationError& e)
      {
        --stats_.rounds.front().filtered;
        e.add_step(std::move(op));
        throw;
      }
    }

    // Lookup for op.
    typename set_type::insert_commit_data commit_data;
    auto insertion = set_.insert_check( op
                                      , std::hash<Operation>()
                                      , [](const Operation& lhs, const cache_entry& rhs)
                                          {return lhs == rhs.operation;}
                                      , commit_data);

    // Check if op has already been computed.
    if (not insertion.second)
    {
      ++stats_.rounds.front().hits;
      // Move cache entry to the end of the LRU list.
      lru_list_.erase(insertion.first->lru_cit_);
      insertion.first->lru_cit_ = lru_list_.insert(lru_list_.end(), &(*insertion.first));
      return insertion.first->result;
    }

    ++stats_.rounds.front().misses;

    cache_entry* entry;
    try
    {
      entry = new cache_entry(std::move(op), op(cxt_));
    }
    catch (EvaluationError& e)
    {
      --stats_.rounds.front().misses;
      e.add_step(std::move(op));
      throw;
    }
    catch (interrupt<result_type>&)
    {
      --stats_.rounds.front().misses;
      throw;
    }

    // Clean up the cache, if necessary.
    if (set_.size() > max_size_)
    {
      auto oldest = lru_list_.front();
      set_.erase(*oldest);
      delete oldest;
      lru_list_.pop_front();
    }

    // Add the new cache entry to the end of the LRU list.
    entry->lru_cit_ = lru_list_.insert(lru_list_.end(), entry);

    // Finally, set the result associated to op.
    set_.insert_commit(*entry, commit_data); // doesn't throw

    return entry->result;
  }

  /// @brief Remove all entries of the cache.
  void
  clear()
  noexcept
  {
    set_.clear_and_dispose([](cache_entry* x){delete x;});
  }

  /// @brief Get the number of cached operations.
  std::size_t
  size()
  const noexcept
  {
    return set_.size();
  }

  /// @brief Get the statistics of this cache.
  const cache_statistics&
  statistics()
  const noexcept
  {
    return stats_;
  }

  /// @brief Get this cache's name
  const std::string&
  name()
  const noexcept
  {
    return name_;
  }
};

/*------------------------------------------------------------------------------------------------*/

}} // namespace sdd::mem

#endif // _SDD_MEM_CACHE_HH_
<commit_msg>Avoid a ever-growing cache.<commit_after>#ifndef _SDD_MEM_CACHE_HH_
#define _SDD_MEM_CACHE_HH_

#include <algorithm> // for_each, nth_element
#include <cstdint>   // uint32_t
#include <forward_list>
#include <iterator>  // distance
#include <list>
#include <numeric>   // accumulate
#include <tuple>
#include <utility>   // forward
#include <vector>

#include "sdd/mem/hash_table.hh"
#include "sdd/mem/interrupt.hh"
#include "sdd/util/hash.hh"
#include "sdd/util/packed.hh"

namespace sdd { namespace mem {

/*------------------------------------------------------------------------------------------------*/

/// @internal
/// @brief Used by cache to know if an operation should be cached or not.
///
/// A filter should always return the same result for the same operation.
template <typename T, typename... Filters>
struct apply_filters;

/// @internal
/// @brief Base case.
///
/// All filters have been applied and they all accepted the operation.
template <typename T>
struct apply_filters<T>
{
  constexpr
  bool
  operator()(const T&)
  const
  {
    return true;
  }
};

/// @internal
/// @brief Recursive case.
///
/// Chain filters calls: as soon as a filter reject an operation, the evaluation is stopped.
template <typename T, typename Filter, typename... Filters>
struct apply_filters<T, Filter, Filters...>
{
  bool
  operator()(const T& op)
  const
  {
    return Filter()(op) ? apply_filters<T, Filters...>()(op) : false;
  }
};

/*------------------------------------------------------------------------------------------------*/

namespace /* anonymous */ {

/// @internal
/// @brief The statistics of a cache.
///
/// A statistic is made of several rounds: each time a cache is cleaned up, a new round is
/// created. Thus, one can have detailed statistics to see how well the cache performed.
struct cache_statistics
{
  /// @internal
  /// @brief Statistic between two cleanups.
  struct round
  {
    round()
      : hits(0)
      , misses(0)
      , filtered(0)
    {}

    /// @brief The number of hits in a round.
    std::size_t hits;

    /// @brief The number of misses in a round.
    std::size_t misses;

    /// @brief The number of filtered entries in a round.
    std::size_t filtered;
  };

  /// @brief The list of all rounds.
  std::forward_list<round> rounds;

  /// @brief Default constructor.
  cache_statistics()
    : rounds(1)
  {}

  /// @brief Get the number of rounds.
  std::size_t
  size()
  const noexcept
  {
    return std::distance(rounds.begin(), rounds.end()); // forward_list lacks a size() function
  }

  /// @brief Get the number of performed cleanups.
  std::size_t
  cleanups()
  const noexcept
  {
    return size() - 1;
  }

  round
  total()
  const noexcept
  {
    return std::accumulate( rounds.begin(), rounds.end(), round()
                          , [&](const round& acc, const round& r) -> round
                               {
                                 round res;
                                 res.hits = acc.hits + r.hits;
                                 res.misses = acc.misses + r.misses;
                                 res.filtered = acc.filtered + r.filtered;
                                 return res;
                               }
                          );
  }
};

} // namespace anonymous

/*------------------------------------------------------------------------------------------------*/

/// @internal
/// @brief  A generic cache.
/// @tparam Operation is the operation type.
/// @tparam EvaluationError is the exception that the evaluation of an Operation can throw.
/// @tparam Filters is a list of filters that reject some operations.
///
/// It uses the LRU strategy to cleanup old entries.
template < typename Context, typename Operation, typename EvaluationError
         , typename... Filters>
class cache
{
  // Can't copy a cache.
  cache(const cache&) = delete;
  cache* operator=(const cache&) = delete;

private:

  /// @brief The type of the context of this cache.
  using context_type = Context;

  /// @brief The type of the result of an operation stored in the cache.
  using result_type = typename Operation::result_type;

  // Forward declaration.
  struct cache_entry;

  /// @brief The type of the container that sorts cache entries by last access date.
  using lru_list_type = std::list<cache_entry*>;

  /// @brief Associate an operation to its result into the cache.
  ///
  /// The operation acts as a key and the associated result is the value counterpart.
  struct
#if defined __clang__
  LIBSDD_ATTRIBUTE_PACKED
#endif
  cache_entry
  {
    // Can't copy a cache_entry.
    cache_entry(const cache_entry&) = delete;
    cache_entry& operator=(const cache_entry&) = delete;

    /// @brief
    mem::intrusive_member_hook<cache_entry> hook;

    /// @brief The cached operation.
    const Operation operation;

    /// @brief The result of the evaluation of operation.
    const result_type result;

    /// @brief Where this cache entry is stored in the LRU list.
    typename lru_list_type::const_iterator lru_cit_;

    /// @brief Constructor.
    template <typename... Args>
    cache_entry(Operation&& op, Args&&... args)
      : hook()
      , operation(std::move(op))
      , result(std::forward<Args>(args)...)
      , lru_cit_()
    {}

    /// @brief Cache entries are only compared using their operations.
    bool
    operator==(const cache_entry& other)
    const noexcept
    {
      return operation == other.operation;
    }
  };

  /// @brief Hash a cache_entry.
  ///
  /// We only use the Operation part of a cache_entry to find it in the cache, in order
  /// to manage the set that stores cache entries as a map.
  struct hash_key
  {
    std::size_t
    operator()(const cache_entry& x)
    const noexcept(noexcept(util::hash(x.operation)))
    {
      return util::hash(x.operation);
    }
  };

  /// @brief An intrusive hash table.
  using set_type = mem::hash_table<cache_entry, hash_key>;

  /// @brief This cache's context.
  context_type& cxt_;

  /// @brief The cache name.
  const std::string name_;

  /// @brief The wanted load factor for the underlying hash table.
  static constexpr double max_load_factor = 0.85;

  /// @brief The actual storage of caches entries.
  set_type set_;

  /// @brief The the container that sorts cache entries by last access date.
  lru_list_type lru_list_;

  /// @brief The maximum size this cache is authorized to grow to.
  std::size_t max_size_;

  /// @brief The statistics of this cache.
  cache_statistics stats_;

public:

  /// @brief Construct a cache.
  /// @param context This cache's context.
  /// @param name Give a name to this cache.
  /// @param size tells how many cache entries are keeped in the cache.
  ///
  /// When the maximal size is reached, a cleanup is launched: half of the cache is removed,
  /// using a LRU strategy. This cache will never perform a rehash, therefore it allocates
  /// all the memory it needs at its construction.
  cache(context_type& context, const std::string& name, std::size_t size)
    : cxt_(context)
    , name_(name)
    , set_(size, max_load_factor, true /* no rehash */)
    , lru_list_()
    , max_size_(set_.bucket_count() * max_load_factor)
    , stats_()
  {}

  /// @brief Destructor.
  ~cache()
  {
    clear();
  }

  /// @brief Cache lookup.
  result_type
  operator()(Operation&& op)
  {
    // Check if the current operation should not be cached.
    if (not apply_filters<Operation, Filters...>()(op))
    {
      ++stats_.rounds.front().filtered;
      try
      {
        return op(cxt_);
      }
      catch (EvaluationError& e)
      {
        --stats_.rounds.front().filtered;
        e.add_step(std::move(op));
        throw;
      }
    }

    // Lookup for op.
    typename set_type::insert_commit_data commit_data;
    auto insertion = set_.insert_check( op
                                      , std::hash<Operation>()
                                      , [](const Operation& lhs, const cache_entry& rhs)
                                          {return lhs == rhs.operation;}
                                      , commit_data);

    // Check if op has already been computed.
    if (not insertion.second)
    {
      ++stats_.rounds.front().hits;
      // Move cache entry to the end of the LRU list.
      lru_list_.erase(insertion.first->lru_cit_);
      insertion.first->lru_cit_ = lru_list_.insert(lru_list_.end(), &(*insertion.first));
      return insertion.first->result;
    }

    ++stats_.rounds.front().misses;

    cache_entry* entry;
    try
    {
      entry = new cache_entry(std::move(op), op(cxt_));
    }
    catch (EvaluationError& e)
    {
      --stats_.rounds.front().misses;
      e.add_step(std::move(op));
      throw;
    }
    catch (interrupt<result_type>&)
    {
      --stats_.rounds.front().misses;
      throw;
    }

    // Clean up the cache, if necessary.
    while (set_.size() > max_size_)
    {
      auto oldest = lru_list_.front();
      set_.erase(*oldest);
      delete oldest;
      lru_list_.pop_front();
    }

    // Add the new cache entry to the end of the LRU list.
    entry->lru_cit_ = lru_list_.insert(lru_list_.end(), entry);

    // Finally, set the result associated to op.
    set_.insert_commit(*entry, commit_data); // doesn't throw

    return entry->result;
  }

  /// @brief Remove all entries of the cache.
  void
  clear()
  noexcept
  {
    set_.clear_and_dispose([](cache_entry* x){delete x;});
  }

  /// @brief Get the number of cached operations.
  std::size_t
  size()
  const noexcept
  {
    return set_.size();
  }

  /// @brief Get the statistics of this cache.
  const cache_statistics&
  statistics()
  const noexcept
  {
    return stats_;
  }

  /// @brief Get this cache's name
  const std::string&
  name()
  const noexcept
  {
    return name_;
  }
};

/*------------------------------------------------------------------------------------------------*/

}} // namespace sdd::mem

#endif // _SDD_MEM_CACHE_HH_
<|endoftext|>


<commit_before><commit_msg>Qt5: fix build failure with Qt 5.15<commit_after><|endoftext|>


<commit_before>/***************************************************************************
 *   Copyright (c) 2002 Jrgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"
#ifndef _PreComp_
# include <QVBoxLayout>
# include <QListWidget>
# include <QListWidgetItem>
# include <QLineEdit>
# include <QTextStream>
# include <QToolButton>
# include <QMenu>
#endif

/// Here the FreeCAD includes sorted by Base,App,Gui......
#include <App/Document.h>
#include "SelectionView.h"
#include "Command.h"
#include "Application.h"
#include "Document.h"
#include "ViewProvider.h"
#include "BitmapFactory.h"

using namespace Gui;
using namespace Gui::DockWnd;

/* TRANSLATOR Gui::DockWnd::SelectionView */

SelectionView::SelectionView(Gui::Document* pcDocument, QWidget *parent)
  : DockWindow(pcDocument,parent)
{
    setWindowTitle(tr("Property View"));

    QVBoxLayout* vLayout = new QVBoxLayout(this);
    vLayout->setSpacing(0);
    vLayout->setMargin (0);

    QLineEdit* searchBox = new QLineEdit(this);
#if QT_VERSION >= 0x040700
    searchBox->setPlaceholderText(tr("Search"));
#endif
    searchBox->setToolTip(tr("Searches object labels"));
    QHBoxLayout* hLayout = new QHBoxLayout(this);
    QToolButton* clearButton = new QToolButton(this);
    clearButton->setFixedSize(18, 21);
    clearButton->setCursor(Qt::ArrowCursor);
    clearButton->setStyleSheet(QString::fromAscii("QToolButton {margin-bottom:6px}"));
    clearButton->setIcon(BitmapFactory().pixmap(":/icons/edit-cleartext.svg"));
    clearButton->setToolTip(tr("Clears the search field"));
    hLayout->addWidget(searchBox);
    hLayout->addWidget(clearButton,0,Qt::AlignRight);
    vLayout->addLayout(hLayout);

    selectionView = new QListWidget(this);
    selectionView->setContextMenuPolicy(Qt::CustomContextMenu);
    vLayout->addWidget( selectionView );
    resize(200, 200);

    connect(clearButton, SIGNAL(clicked()), searchBox, SLOT(clear()));
    connect(searchBox, SIGNAL(textChanged(QString)), this, SLOT(search(QString)));
    connect(selectionView, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(select(QListWidgetItem*)));
    connect(selectionView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onItemContextMenu(QPoint)));

    Gui::Selection().Attach(this);
}

SelectionView::~SelectionView()
{
    Gui::Selection().Detach(this);
}

/// @cond DOXERR
void SelectionView::OnChange(Gui::SelectionSingleton::SubjectType &rCaller,
                             Gui::SelectionSingleton::MessageType Reason)
{
    std::string temp;

    if (Reason.Type == SelectionChanges::AddSelection) {
        // insert the selection as item
        temp = Reason.pDocName;
        temp += ".";
        temp += Reason.pObjectName;
        if (Reason.pSubName[0] != 0 ) {
            temp += ".";
            temp += Reason.pSubName;
        }
        App::Document* doc = App::GetApplication().getDocument(Reason.pDocName);
        App::DocumentObject* obj = doc->getObject(Reason.pObjectName);
        temp += " (";
        temp += obj->Label.getValue();
        temp += ")";

        new QListWidgetItem(QString::fromAscii(temp.c_str()), selectionView);
    }
    else if (Reason.Type == SelectionChanges::ClrSelection) {
        // remove all items
        selectionView->clear();
    }
    else if (Reason.Type == SelectionChanges::RmvSelection) {
        // build name
        temp = Reason.pDocName;
        temp += ".";
        temp += Reason.pObjectName;
        if (Reason.pSubName[0] != 0) {
            temp += ".";
            temp += Reason.pSubName;
        }
        App::Document* doc = App::GetApplication().getDocument(Reason.pDocName);
        App::DocumentObject* obj = doc->getObject(Reason.pObjectName);
        temp += " (";
        temp += obj->Label.getValue();
        temp += ")";

        // remove all items
        QList<QListWidgetItem *> l = selectionView->findItems(QLatin1String(temp.c_str()),Qt::MatchExactly);
        if (l.size() == 1)
            delete l[0];

    }
    else if (Reason.Type == SelectionChanges::SetSelection) {
        // remove all items
        selectionView->clear();
        std::vector<SelectionSingleton::SelObj> objs = Gui::Selection().getSelection(Reason.pDocName);
        for (std::vector<SelectionSingleton::SelObj>::iterator it = objs.begin(); it != objs.end(); ++it) {
            // build name
            temp = it->DocName;
            temp += ".";
            temp += it->FeatName;
            if (it->SubName && it->SubName[0] != '\0') {
                temp += ".";
                temp += it->SubName;
            }
            App::Document* doc = App::GetApplication().getDocument(it->DocName);
            App::DocumentObject* obj = doc->getObject(it->FeatName);
            temp += " (";
            temp += obj->Label.getValue();
            temp += ")";

            new QListWidgetItem(QString::fromAscii(temp.c_str()), selectionView);
        }
    }
}

void SelectionView::search(const QString& text)
{
    if (!text.isEmpty()) {
        App::Document* doc = App::GetApplication().getActiveDocument();
        std::vector<App::DocumentObject*> objects;
        if (doc) {
            Gui::Selection().clearSelection();
            objects = doc->getObjects();
            for (std::vector<App::DocumentObject*>::iterator it = objects.begin(); it != objects.end(); ++it) {
                QString label = QString::fromUtf8((*it)->Label.getValue());
                if (label.contains(text,Qt::CaseInsensitive)) {
                    if (!Gui::Selection().hasSelection((*it)->getNameInDocument())) {
                        Gui::Selection().addSelection(doc->getName(),(*it)->getNameInDocument(),0);
                    }
                }
            }
        }
    }
}

void SelectionView::select(QListWidgetItem* item)
{
    if (!item)
        item = selectionView->currentItem();
    if (!item)
        return;
    QStringList elements = item->text().split(QString::fromAscii("."));
    // remove possible space from object name followed by label
    elements[1] = elements[1].split(QString::fromAscii(" "))[0];
    //Gui::Selection().clearSelection();
    Gui::Command::runCommand(Gui::Command::Gui,"Gui.Selection.clearSelection()");
    //Gui::Selection().addSelection(elements[0].toAscii(),elements[1].toAscii(),0);
    QString cmd = QString::fromAscii("Gui.Selection.addSelection(App.getDocument(\"%1\").getObject(\"%2\"))").arg(elements[0]).arg(elements[1]);
    Gui::Command::runCommand(Gui::Command::Gui,cmd.toAscii());
}

void SelectionView::deselect(void)
{
    QListWidgetItem *item = selectionView->currentItem();
    if (!item)
        return;
    QStringList elements = item->text().split(QString::fromAscii("."));
    // remove possible space from object name followed by label
    elements[1] = elements[1].split(QString::fromAscii(" "))[0];
    //Gui::Selection().rmvSelection(elements[0].toAscii(),elements[1].toAscii(),0);
    QString cmd = QString::fromAscii("Gui.Selection.removeSelection(App.getDocument(\"%1\").getObject(\"%2\"))").arg(elements[0]).arg(elements[1]);
    Gui::Command::runCommand(Gui::Command::Gui,cmd.toAscii());
}

void SelectionView::zoom(void)
{
    select();
    Gui::Command::runCommand(Gui::Command::Gui,"Gui.SendMsgToActiveView(\"ViewSelection\")");
}

void SelectionView::treeSelect(void)
{
    select();
    Gui::Command::runCommand(Gui::Command::Gui,"Gui.runCommand(\"Std_TreeSelection\")");
}

void SelectionView::onItemContextMenu(const QPoint& point)
{
    QListWidgetItem *item = selectionView->itemAt(point);
    if (!item)
        return;
    QMenu menu;
    QAction *selectAction = menu.addAction(tr("Select only"),this,SLOT(select()));
    selectAction->setIcon(QIcon(QString::fromAscii(":/icons/view-select.svg")));
    selectAction->setToolTip(tr("Selects only this object"));
    QAction *deselectAction = menu.addAction(tr("Deselect"),this,SLOT(deselect()));
    deselectAction->setIcon(QIcon(QString::fromAscii(":/icons/view-unselectable.svg")));
    deselectAction->setToolTip(tr("Deselects this object"));
    QAction *zoomAction = menu.addAction(tr("Zoom fit"),this,SLOT(zoom()));
    zoomAction->setIcon(QIcon(QString::fromAscii(":/icons/view-zoom-fit.svg")));
    zoomAction->setToolTip(tr("Selects and fits this object in the 3D window"));
    QAction *gotoAction = menu.addAction(tr("Go to selection"),this,SLOT(treeSelect()));
    gotoAction->setToolTip(tr("Selects and locates this object in the tree view"));
    menu.exec(selectionView->mapToGlobal(point));
}

void SelectionView::onUpdate(void)
{
}

bool SelectionView::onMsg(const char* pMsg,const char** ppReturn)
{
    return false;
}
/// @endcond

#include "moc_SelectionView.cpp"
<commit_msg>+ fixes #0001896: Selection View widget don't display latin characters for label.<commit_after>/***************************************************************************
 *   Copyright (c) 2002 Jrgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"
#ifndef _PreComp_
# include <QVBoxLayout>
# include <QListWidget>
# include <QListWidgetItem>
# include <QLineEdit>
# include <QTextStream>
# include <QToolButton>
# include <QMenu>
#endif

/// Here the FreeCAD includes sorted by Base,App,Gui......
#include <App/Document.h>
#include "SelectionView.h"
#include "Command.h"
#include "Application.h"
#include "Document.h"
#include "ViewProvider.h"
#include "BitmapFactory.h"

using namespace Gui;
using namespace Gui::DockWnd;

/* TRANSLATOR Gui::DockWnd::SelectionView */

SelectionView::SelectionView(Gui::Document* pcDocument, QWidget *parent)
  : DockWindow(pcDocument,parent)
{
    setWindowTitle(tr("Property View"));

    QVBoxLayout* vLayout = new QVBoxLayout(this);
    vLayout->setSpacing(0);
    vLayout->setMargin (0);

    QLineEdit* searchBox = new QLineEdit(this);
#if QT_VERSION >= 0x040700
    searchBox->setPlaceholderText(tr("Search"));
#endif
    searchBox->setToolTip(tr("Searches object labels"));
    QHBoxLayout* hLayout = new QHBoxLayout(this);
    QToolButton* clearButton = new QToolButton(this);
    clearButton->setFixedSize(18, 21);
    clearButton->setCursor(Qt::ArrowCursor);
    clearButton->setStyleSheet(QString::fromAscii("QToolButton {margin-bottom:6px}"));
    clearButton->setIcon(BitmapFactory().pixmap(":/icons/edit-cleartext.svg"));
    clearButton->setToolTip(tr("Clears the search field"));
    hLayout->addWidget(searchBox);
    hLayout->addWidget(clearButton,0,Qt::AlignRight);
    vLayout->addLayout(hLayout);

    selectionView = new QListWidget(this);
    selectionView->setContextMenuPolicy(Qt::CustomContextMenu);
    vLayout->addWidget( selectionView );
    resize(200, 200);

    connect(clearButton, SIGNAL(clicked()), searchBox, SLOT(clear()));
    connect(searchBox, SIGNAL(textChanged(QString)), this, SLOT(search(QString)));
    connect(selectionView, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(select(QListWidgetItem*)));
    connect(selectionView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onItemContextMenu(QPoint)));

    Gui::Selection().Attach(this);
}

SelectionView::~SelectionView()
{
    Gui::Selection().Detach(this);
}

/// @cond DOXERR
void SelectionView::OnChange(Gui::SelectionSingleton::SubjectType &rCaller,
                             Gui::SelectionSingleton::MessageType Reason)
{
    QString selObject;
    QTextStream str(&selObject);
    if (Reason.Type == SelectionChanges::AddSelection) {
        // insert the selection as item
        str << Reason.pDocName;
        str << ".";
        str << Reason.pObjectName;
        if (Reason.pSubName[0] != 0 ) {
            str << ".";
            str << Reason.pSubName;
        }
        App::Document* doc = App::GetApplication().getDocument(Reason.pDocName);
        App::DocumentObject* obj = doc->getObject(Reason.pObjectName);
        str << " (";
        str << QString::fromUtf8(obj->Label.getValue());
        str << ")";

        new QListWidgetItem(selObject, selectionView);
    }
    else if (Reason.Type == SelectionChanges::ClrSelection) {
        // remove all items
        selectionView->clear();
    }
    else if (Reason.Type == SelectionChanges::RmvSelection) {
        // build name
        str << Reason.pDocName;
        str << ".";
        str << Reason.pObjectName;
        if (Reason.pSubName[0] != 0) {
            str << ".";
            str << Reason.pSubName;
        }
        App::Document* doc = App::GetApplication().getDocument(Reason.pDocName);
        App::DocumentObject* obj = doc->getObject(Reason.pObjectName);
        str << " (";
        str << QString::fromUtf8(obj->Label.getValue());
        str << ")";

        // remove all items
        QList<QListWidgetItem *> l = selectionView->findItems(selObject,Qt::MatchExactly);
        if (l.size() == 1)
            delete l[0];

    }
    else if (Reason.Type == SelectionChanges::SetSelection) {
        // remove all items
        selectionView->clear();
        std::vector<SelectionSingleton::SelObj> objs = Gui::Selection().getSelection(Reason.pDocName);
        for (std::vector<SelectionSingleton::SelObj>::iterator it = objs.begin(); it != objs.end(); ++it) {
            // build name
            str << it->DocName;
            str << ".";
            str << it->FeatName;
            if (it->SubName && it->SubName[0] != '\0') {
                str << ".";
                str << it->SubName;
            }
            App::Document* doc = App::GetApplication().getDocument(it->DocName);
            App::DocumentObject* obj = doc->getObject(it->FeatName);
            str << " (";
            str << QString::fromUtf8(obj->Label.getValue());
            str << ")";

            new QListWidgetItem(selObject, selectionView);
        }
    }
}

void SelectionView::search(const QString& text)
{
    if (!text.isEmpty()) {
        App::Document* doc = App::GetApplication().getActiveDocument();
        std::vector<App::DocumentObject*> objects;
        if (doc) {
            Gui::Selection().clearSelection();
            objects = doc->getObjects();
            for (std::vector<App::DocumentObject*>::iterator it = objects.begin(); it != objects.end(); ++it) {
                QString label = QString::fromUtf8((*it)->Label.getValue());
                if (label.contains(text,Qt::CaseInsensitive)) {
                    if (!Gui::Selection().hasSelection((*it)->getNameInDocument())) {
                        Gui::Selection().addSelection(doc->getName(),(*it)->getNameInDocument(),0);
                    }
                }
            }
        }
    }
}

void SelectionView::select(QListWidgetItem* item)
{
    if (!item)
        item = selectionView->currentItem();
    if (!item)
        return;
    QStringList elements = item->text().split(QString::fromAscii("."));
    // remove possible space from object name followed by label
    elements[1] = elements[1].split(QString::fromAscii(" "))[0];
    //Gui::Selection().clearSelection();
    Gui::Command::runCommand(Gui::Command::Gui,"Gui.Selection.clearSelection()");
    //Gui::Selection().addSelection(elements[0].toAscii(),elements[1].toAscii(),0);
    QString cmd = QString::fromAscii("Gui.Selection.addSelection(App.getDocument(\"%1\").getObject(\"%2\"))").arg(elements[0]).arg(elements[1]);
    Gui::Command::runCommand(Gui::Command::Gui,cmd.toAscii());
}

void SelectionView::deselect(void)
{
    QListWidgetItem *item = selectionView->currentItem();
    if (!item)
        return;
    QStringList elements = item->text().split(QString::fromAscii("."));
    // remove possible space from object name followed by label
    elements[1] = elements[1].split(QString::fromAscii(" "))[0];
    //Gui::Selection().rmvSelection(elements[0].toAscii(),elements[1].toAscii(),0);
    QString cmd = QString::fromAscii("Gui.Selection.removeSelection(App.getDocument(\"%1\").getObject(\"%2\"))").arg(elements[0]).arg(elements[1]);
    Gui::Command::runCommand(Gui::Command::Gui,cmd.toAscii());
}

void SelectionView::zoom(void)
{
    select();
    Gui::Command::runCommand(Gui::Command::Gui,"Gui.SendMsgToActiveView(\"ViewSelection\")");
}

void SelectionView::treeSelect(void)
{
    select();
    Gui::Command::runCommand(Gui::Command::Gui,"Gui.runCommand(\"Std_TreeSelection\")");
}

void SelectionView::onItemContextMenu(const QPoint& point)
{
    QListWidgetItem *item = selectionView->itemAt(point);
    if (!item)
        return;
    QMenu menu;
    QAction *selectAction = menu.addAction(tr("Select only"),this,SLOT(select()));
    selectAction->setIcon(QIcon(QString::fromAscii(":/icons/view-select.svg")));
    selectAction->setToolTip(tr("Selects only this object"));
    QAction *deselectAction = menu.addAction(tr("Deselect"),this,SLOT(deselect()));
    deselectAction->setIcon(QIcon(QString::fromAscii(":/icons/view-unselectable.svg")));
    deselectAction->setToolTip(tr("Deselects this object"));
    QAction *zoomAction = menu.addAction(tr("Zoom fit"),this,SLOT(zoom()));
    zoomAction->setIcon(QIcon(QString::fromAscii(":/icons/view-zoom-fit.svg")));
    zoomAction->setToolTip(tr("Selects and fits this object in the 3D window"));
    QAction *gotoAction = menu.addAction(tr("Go to selection"),this,SLOT(treeSelect()));
    gotoAction->setToolTip(tr("Selects and locates this object in the tree view"));
    menu.exec(selectionView->mapToGlobal(point));
}

void SelectionView::onUpdate(void)
{
}

bool SelectionView::onMsg(const char* pMsg,const char** ppReturn)
{
    return false;
}
/// @endcond

#include "moc_SelectionView.cpp"
<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  $RCSfile: ignoreBaFa_ja_JP.cxx,v $
 *
 *  $Revision: 1.6 $
 *
 *  last change: $Author: hr $ $Date: 2003-04-28 16:50:29 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

// prevent internal compiler error with MSVC6SP3
#include <utility>

#define TRANSLITERATION_BaFa_ja_JP
#include <transliteration_Ignore.hxx>

using namespace com::sun::star::uno;
using namespace rtl;

namespace com { namespace sun { namespace star { namespace i18n {

static Mapping BaFa[] = {
        { 0x30F4, 0x30A1, 0x30D0 },
        { 0x3094, 0x3041, 0x3070 },
        { 0x30D5, 0x30A1, 0x30CF },
        { 0x3075, 0x3041, 0x306F },
        { 0, 0, 0 }
};

ignoreBaFa_ja_JP::ignoreBaFa_ja_JP()
{
        func = (TransFunc) 0;
        table = 0;
        map = BaFa;
        transliterationName = "ignoreBaFa_ja_JP";
        implementationName = "com.sun.star.i18n.Transliteration.ignoreBaFa_ja_JP";
}

} } } }
<commit_msg>INTEGRATION: CWS i18n04 (1.6.4); FILE MERGED 2003/05/09 21:35:09 khong 1.6.4.1: #109508# fix problem for Japanese search<commit_after>/*************************************************************************
 *
 *  $RCSfile: ignoreBaFa_ja_JP.cxx,v $
 *
 *  $Revision: 1.7 $
 *
 *  last change: $Author: rt $ $Date: 2003-05-21 08:05:43 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

// prevent internal compiler error with MSVC6SP3
#include <utility>

#define TRANSLITERATION_BaFa_ja_JP
#include <transliteration_Ignore.hxx>

using namespace com::sun::star::uno;
using namespace rtl;

namespace com { namespace sun { namespace star { namespace i18n {

static Mapping BaFa[] = {
        { 0x30F4, 0x30A1, 0x30D0, sal_True },
        { 0x3094, 0x3041, 0x3070, sal_True },
        { 0x30D5, 0x30A1, 0x30CF, sal_True },
        { 0x3075, 0x3041, 0x306F, sal_True },
        { 0, 0, 0, sal_True }
};

ignoreBaFa_ja_JP::ignoreBaFa_ja_JP()
{
        func = (TransFunc) 0;
        table = 0;
        map = BaFa;
        transliterationName = "ignoreBaFa_ja_JP";
        implementationName = "com.sun.star.i18n.Transliteration.ignoreBaFa_ja_JP";
}

} } } }
<|endoftext|>