text:
//
// Copyright (C) 2015 Tokyo Institute of Technology
//
// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include <sys/stat.h>

#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>

#include <netinet/in.h>
#include <arpa/inet.h>

#include "common/error.h"
#include "common/common.h"
#include "util/hash/hash_calc_md5.h"
#include "util/mutex.h"
#include "burst_buffer_client.h"

static cbb::Mutex g_fuse_mutex;

#define USE_SESSION_POOL_FOR_IO

#define RETRY_MAX 3       // 再実行の上限回数
#define RETRY_INTERVAL 1  // 再実行の間隔(秒

#define RPC_RETRY_EXCEPTION_COMMON_HANDLER()  \
  /* 再実行回数の上限に達したら諦める */               \
  if (--retry_count <= 0) throw;              \
                                                     \
  /* 再実行の準備: 一旦 接続を明示的に破棄 */          \
  c.close();                                  \
                                                     \
  /* 間隔をおいて再実行 */                            \
  std::cerr << e.what() << std::endl;         \
  ::sleep(RETRY_INTERVAL);                    \
  continue

#define MSGPACK_CLIENT_CALL(operation)                      \
  try {                                                     \
    int retry_count = 0;                                    \
    g_fuse_mutex.Lock();                                    \
    while (true) {                                          \
      try {                                                 \
        operation ;                                         \
        break;                                              \
      } catch (msgpack::rpc::connection_closed_error &e) {  \
        RPC_RETRY_EXCEPTION_COMMON_HANDLER();               \
      } catch (msgpack::rpc::system_error &e) {             \
        RPC_RETRY_EXCEPTION_COMMON_HANDLER();               \
      } catch (msgpack::rpc::timeout_error &e) {            \
        RPC_RETRY_EXCEPTION_COMMON_HANDLER();               \
      }                                                              \
    }                                                                \
    g_fuse_mutex.Unlock();                                  \
  } catch (msgpack::rpc::remote_error &e) {                 \
    g_fuse_mutex.Unlock();                                  \
    std::cerr << "Error " << e.what() << std::endl;         \
    abort();                                                \
  }

uint32_t addr_to_binary(const char *ipv4_addr) {
  struct in_addr addr;
	int ret = inet_aton(ipv4_addr, &addr);
	if (ret == 0) {
	  return -1;
	}
	return addr.s_addr;
}

// CBFSモジュール（クライアント側）のメイン処理クラス
namespace cbb {

/**
 * @breaf constractor
 */
BurstBufferClient::BurstBufferClient() {
	Thread::Init();
}

/**
 * @breaf destractor
 */
BurstBufferClient::~BurstBufferClient() {
	Thread::Release();
}

/**
 * @breaf ファイル属性取得
 * @param path ファイルパス
 * @param file_stat_ptr ファイルステータス構造体ポインタ
 * @return Error値
 */
Error BurstBufferClient::GetAttr(const char *path, FileStat *file_stat_ptr) {
DMSG("GetAttr [%s] - ", path);
  std::string link_path;
  return GetAttrEx(path, file_stat_ptr, link_path);
}

/**
 * @breaf ファイル属性取得
 * @param path ファイルパス
 * @param file_stat_ptr ファイルステータス構造体ポインタ
 * @return Error値
 */
Error BurstBufferClient::GetAttrExInternal(const char *path, FileStat *file_stat_ptr, std::string &link_path) {
DMSG("GetAttrExInternal [%s] \n", path);

  link_path = "";

  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  typedef msgpack::type::tuple<Error, FileStat, std::string> Result;
  MSGPACK_CLIENT_CALL(
      Result result  = c.call(CODE(kGetAttr), std::string(path)).get<Result>();
      error = result.get<0>();
      *file_stat_ptr = result.get<1>();
      link_path = result.get<2>();
  );

  return error;
}

/**
 * @breaf ファイル属性取得
 * @param path ファイルパス
 * @param file_stat_ptr ファイルステータス構造体ポインタ
 * @return Error値
 */
Error BurstBufferClient::GetAttrEx(const char *path, FileStat *file_stat_ptr, std::string &link_path) {
DMSG("GetAttrEx [%s] \n", path);

  // virtual symlink check
  std::string target = create_virtual_symlink(path);
  Error error = GetAttrExInternal(target.c_str(), file_stat_ptr, link_path);

  if (error < 0) {
    // virtual link check
    target = create_virtual_link(path);
    error = GetAttrExInternal(target.c_str(), file_stat_ptr, link_path);
    if (error < 0) {
      error = GetAttrExInternal(path, file_stat_ptr, link_path);
    } else if (!link_path.empty()) {
      target = link_path;
      GetAttrExInternal(target.c_str(), file_stat_ptr, link_path);
//      file_stat_ptr->st_mode |= S_IFLNK; // symlink属性付加
    }
  } else if (!link_path.empty()) {
    file_stat_ptr->st_mode |= S_IFLNK; // symlink属性付加
  }

  return error;
}

/**
 * @breaf リンク情報取得
 * @param path ファイルパス
 * @param buf バッファポインタ
 * @param size バッファサイズ
 * @return Error値
 */
Error BurstBufferClient::ReadLink(const char *path, char *buf, size_t size) {
  *buf = NULL;

  std::string link_path;
  FileStat file_stat;

  // virtual symlink check
  std::string target = create_virtual_symlink(path);
  Error error = GetAttrExInternal(target.c_str(), &file_stat, link_path);

  if (error < 0) {
    // virtual link check
    target = create_virtual_link(path);
    error = GetAttrExInternal(target.c_str(), &file_stat, link_path);

    if (error < 0) {
      link_path = "";

      std::string bb_host;
      uint16_t bb_port;

      error = GetBurstBuffer(path, &bb_host, &bb_port);
      if (error != kCBBSuccess)
        return error;

#ifdef USE_SESSION_POOL_FOR_IO
      msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
      msgpack::rpc::client c(bb_host, bb_port);
#endif

      msgpack::rpc::auto_zone zone;

      typedef msgpack::type::tuple<Error, msgpack::type::raw_ref> Result;
      MSGPACK_CLIENT_CALL(
          Result result = c.call(CODE(kReadLink), std::string(path), size).get<Result>(&zone);
          error = result.get<0>();
          msgpack::type::raw_ref data = result.get<1>();
          std::memcpy(buf, data.ptr, data.size);
      );
    } else if (!link_path.empty()) {
      strcpy(buf, link_path.c_str());
    }
  } else if (!link_path.empty()) {
    strcpy(buf, link_path.c_str());
  }

  return (error >= 0 ? 0: error);
}

/**
 * @breaf ディレクトリ作成
 * @param path ファイルパス
 * @param mode モード値
 * @return Error値
 */
Error BurstBufferClient::MkDir(const char *path, mode_t mode) {
  Error error;

  BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {

#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
    msgpack::rpc::client c(info.host, info.port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kMkDir), std::string(path), mode).get<Error>();
    );
  }

  return error;
}

/**
 * @breaf ファイル削除
 * @param path ファイルパス
 * @return Error値
 */
Error BurstBufferClient::UnlinkInternal(const char *path, bool is_all_server) {
DMSG("UnlinkInternal [%s] \n", path);
  Error error;

  if (is_all_server) {
    BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {

#ifdef USE_SESSION_POOL_FOR_IO
      msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
      msgpack::rpc::client c(info.host, info.port);
#endif

      MSGPACK_CLIENT_CALL(
          error = c.call(CODE(kUnlink), std::string(path)).get<Error>();
      );
    }
  } else {

    std::string bb_host;
    uint16_t bb_port;

    error = GetBurstBuffer(path, &bb_host, &bb_port);
    if (error != kCBBSuccess)
      return error;

#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
    msgpack::rpc::client c(bb_host, bb_port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kUnlink), std::string(path)).get<Error>();
    );
  }

  return error;
}

/**
 * @breaf ファイル削除
 * @param path ファイルパス
 * @return Error値
 */
Error BurstBufferClient::Unlink(const char *path) {
DMSG("Unlink [%s] \n", path);
  FileStat file_stat;
  std::string link_path;

  // virtual symlink check
  std::string target = create_virtual_symlink(path);
  Error error = GetAttrExInternal(target.c_str(), &file_stat, link_path);

  if (error < 0) {
    // virtual link check
    target = create_virtual_link(path);
    error = GetAttrExInternal(target.c_str(), &file_stat, link_path);
    if (error < 0) {
      error = UnlinkInternal(path, true);
    } else {
      error = UnlinkInternal(target.c_str(), false);
    }
  } else {
    error = UnlinkInternal(target.c_str(), false);
  }

  return error;
}

/**
 * @breaf ディレクトリ削除
 * @param path ファイルパス
 * @return Error値
 */
Error BurstBufferClient::RmDir(const char *path) {
  Error error;

  BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {

#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
    msgpack::rpc::client c(info.host, info.port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kRmDir), std::string(path)).get<Error>();
    );
  }

  return error;
}

/**
 * @breaf シンボリックリンク
 * @param path ファイルパス
 * @param link リンク
 * @return Error値
 */
Error BurstBufferClient::Symlink(const char *path, const char *link) {
  Error error;
  FileStat file_stat;
  std::string link_path;

  error = GetAttrEx(path, &file_stat, link_path);

  // ディレクトリの場合
  if (error >= 0 && S_ISDIR(file_stat.st_mode)) {
//DMSG("Symlink - Dir \n");

    BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {

#ifdef USE_SESSION_POOL_FOR_IO
      msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
      msgpack::rpc::client c(info.host, info.port);
#endif

      MSGPACK_CLIENT_CALL(
          error = c.call(CODE(kSymlink), std::string(path), std::string(link)).get<Error>();
      );
    }

  } else {
//DMSG("Symlink - Virtual Symlink \n");

    std::string bb_host;
    uint16_t bb_port;

    link_path = create_virtual_symlink(link);
    Error error = GetBurstBuffer(link_path.c_str(), &bb_host, &bb_port);
    if (error != kCBBSuccess)
      return error;

#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
    msgpack::rpc::client c(bb_host, bb_port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kSymlink), std::string(path), link_path).get<Error>();
    );
  }

  return error;
}

/**
 * @breaf リネーム
 * @param old_path 変更前ファイルパス
 * @param new_path 変更後ファイルパス
 * @return Error値
 */
Error BurstBufferClient::Rename(const char *old_path, const char *new_path) {
  Error error;

  // ファイル、ディレクトリチェック
  FileStat file_stat;
  std::string link_path;

  error = GetAttrEx(old_path, &file_stat, link_path);

  // ディレクトリの場合
  if (error >= 0 && S_ISDIR(file_stat.st_mode)) {
    // すべてのサーバーにリネーム命令を実行する
    BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {

#ifdef USE_SESSION_POOL_FOR_IO
      msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
      msgpack::rpc::client c(info.host, info.port);
#endif

      MSGPACK_CLIENT_CALL(
          error = c.call(CODE(kRename), std::string(old_path), std::string(new_path)).get<Error>();
      );
    }

  } else {
    std::string bb_host;
    uint16_t bb_port;

    Error error = GetBurstBuffer(old_path, &bb_host, &bb_port);
    if (error != kCBBSuccess)
      return error;

#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
    msgpack::rpc::client c(bb_host, bb_port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kRename), std::string(old_path), std::string(new_path)).get<Error>();
    );
  }

  return error;
}

/**
 * @breaf リンク
 * @param path ファイルパス
 * @param newpath 新ファイルパス
 * @return Error値
 */
Error BurstBufferClient::Link(const char *path, const char *newpath) {
  Error error;
  FileStat file_stat;
  std::string link_path;

  error = GetAttrEx(path, &file_stat, link_path);

  // ディレクトリの場合
  if (S_ISDIR(file_stat.st_mode)) {
//DMSG("Link - Dir \n");

    BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {

#ifdef USE_SESSION_POOL_FOR_IO
      msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
      msgpack::rpc::client c(info.host, info.port);
#endif

      MSGPACK_CLIENT_CALL(
          error = c.call(CODE(kLink), std::string(path), std::string(newpath)).get<Error>();
      );
    }

  } else {
//DMSG("Link - Virtual Link \n");

    std::string bb_host;
    uint16_t bb_port;
    std::string link_path = create_virtual_link(newpath);

    Error error = GetBurstBuffer(link_path.c_str(), &bb_host, &bb_port);
    if (error != kCBBSuccess)
      return error;

#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
    msgpack::rpc::client c(bb_host, bb_port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kLink), std::string(path), link_path).get<Error>();
    );
  }

  return error;
}

/**
 * @breaf ファイル属性変更
 * @param path ファイルパス
 * @param mode モード値
 * @return Error値
 */
Error BurstBufferClient::Chmod(const char *path, mode_t mode) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kChmod), std::string(path), mode).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイルオーナー変更
 * @param path ファイルパス
 * @param uid UID
 * @param gid GID
 * @return Error値
 */
Error BurstBufferClient::Chown(const char *path, uid_t uid, gid_t gid) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kChown), std::string(path), uid, gid).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイルサイズ変更
 * @param path ファイルパス
 * @param size サイズ
 * @return Error値
 */
Error BurstBufferClient::Truncate(const char *path, off_t size) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kTruncate), std::string(path), size).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイルオープン
 * @param path ファイルパス
 * @param flags フラグ
 * @param file_ptr ファイル情報ポインタ
 * @return Error値
 */
Error BurstBufferClient::Open(const char* path, int flags, File *file_ptr) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

  file_ptr->path = path;
  file_ptr->bb_host = bb_host;
  file_ptr->bb_port = bb_port;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  int fd = -1;
  MSGPACK_CLIENT_CALL(
      fd = c.call(CODE(kOpen), std::string(path), flags).get<int>();
  );

  if (fd < 0)
    return static_cast<Error>(fd);

  file_ptr->fd_org = fd;
  file_ptr->fd = ((uint64_t)addr_to_binary(bb_host.c_str()) << 32) | fd;

  // 先読み開始
  StartPrevFileRead(path);

  return kCBBSuccess;
}

/**
 * @breaf ファイル読み込み
 * @param path ファイルパス
 * @param buf バッファポインタ
 * @param size サイズ
 * @param offset オフセット
 * @param ssize_ptr サイズ保存ポインタ
 * @return Error値
 */
Error BurstBufferClient::Read(const File &file, char *buf, size_t size, off_t offset, ssize_t *ssize_ptr) {

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(file.bb_host, file.bb_port);
#else
  msgpack::rpc::client c(file.bb_host, file.bb_port);
#endif

  ssize_t ssize = 0;
  msgpack::type::raw_ref raw;
  msgpack::rpc::auto_zone zone;

  typedef msgpack::type::tuple<ssize_t, msgpack::type::raw_ref> Result;
  MSGPACK_CLIENT_CALL(
      Result result =  c.call(CODE(kRead), file.path, file.fd_org, size, offset).get<Result>(&zone);
      ssize = result.get<0>();
      raw = result.get<1>();
  );

  if (ssize < 0)
    return static_cast<Error>(ssize);

  *ssize_ptr = ssize;
  std::memcpy(buf, raw.ptr, raw.size);

  return kCBBSuccess;
}

/**
 * @breaf ファイル書き込み
 * @param file ファイル情報
 * @param buf バッファポインタ
 * @param size サイズ
 * @param offset オフセット
 * @param ssize_ptr サイズ保存ポインタ
 * @return Error値
 */
Error BurstBufferClient::Write(const File &file, const char *buf, size_t size, off_t offset, ssize_t *ssize_ptr) {

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(file.bb_host, file.bb_port);
#else
  msgpack::rpc::client c(file.bb_host, file.bb_port);
#endif

  msgpack::type::raw_ref raw(buf, size);

  ssize_t ssize = 0;
  MSGPACK_CLIENT_CALL(
      ssize = c.call(CODE(kWrite), file.path, file.fd_org, offset, raw).get<ssize_t>();
  );

  if (ssize < 0)
    return static_cast<Error>(ssize);

  *ssize_ptr = ssize;

  return kCBBSuccess;
}

/**
 * @breaf StatFs
 * @param path ファイルパス
 * @param buf statvfs情報
 * @return Error値
 */
Error BurstBufferClient::StatFs(const char *path, struct statvfs *buf) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  typedef msgpack::type::tuple<Error, StatVfs> Result;
  MSGPACK_CLIENT_CALL(
      Result result  = c.call(CODE(kStatFs), std::string(path)).get<Result>();
      error = result.get<0>();
      *buf = result.get<1>();
  );

  return error;
}

/**
 * @breaf ファイルフラッシュ
 * @param file ファイル情報
 * @return Error値
 */
Error BurstBufferClient::Flush(const File &file) {

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(file.bb_host, file.bb_port);
#else
  msgpack::rpc::client c(file.bb_host, file.bb_port);
#endif

  Error error = kCBBSuccess;
  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kFlush), file.path, static_cast<int>(file.fd_org)).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイル開放（クローズ）
 * @param file ファイル情報
 * @return Error値
 */
Error BurstBufferClient::Release(const File &file) {

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(file.bb_host, file.bb_port);
#else
  msgpack::rpc::client c(file.bb_host, file.bb_port);
#endif

  Error error = kCBBSuccess;
  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kRelease), file.path, static_cast<int>(file.fd_org)).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイル同期
 * @param file ファイル情報
 * @param datasync データ同期
 * @return Error値
 */
Error BurstBufferClient::FSync(const File &file, int datasync) {

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(file.bb_host, file.bb_port);
#else
  msgpack::rpc::client c(file.bb_host, file.bb_port);
#endif

  Error error = kCBBSuccess;
  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kFSync), file.path, static_cast<int>(file.fd_org), datasync).get<Error>();
  );

  return error;
}

/**
 * @breaf ディレクトリリスト読み込み
 * @param path ファイルパス
 * @param offset オフセット
 * @param file_stats_ptr ファイル情報配列ポインタ
 * @return Error値
 */
Error BurstBufferClient::ReadDir(const char *path, off_t offset, FileStats *file_stats_ptr, int type) {
  Error error;

  file_stats_ptr->clear();

  BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {
#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
    msgpack::rpc::client c(info.host, info.port);
#endif

    FileStats file_stats;

    typedef msgpack::type::tuple<Error, FileStats> Result;
    MSGPACK_CLIENT_CALL(
      Result result = c.call(CODE(kReadDir), std::string(path), offset, type).get<Result>();
      error = result.get<0>();
      file_stats = result.get<1>();
    );

    file_stats_ptr->insert(file_stats.begin(), file_stats.end());
  }

  return error;
}

/**
 * @breaf ディレクトリ同期
 * @param path ディレクトリパス
 * @param datasync データ同期
 * @param file ファイル情報
 * @return Error値
 */
Error BurstBufferClient::FSyncDir(const char *path, int datasync, const File &file) {
  Error error;

  BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {
#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
    msgpack::rpc::client c(info.host, info.port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kFSync), std::string(path), datasync).get<Error>();
    );
  }

  return error;
}

/**
 * @breaf 拡張ファイル属性設定
 * @param path ファイルパス
 * @param name 名前
 * @param value 設定値
 * @param size サイズ
 * @param flags フラグ
 * @return Error値
 */
Error BurstBufferClient::SetXAttr(const char *path, const char *name, const char *value, size_t size, int flags) {
  Error error;

  // ファイル、ディレクトリチェック
  FileStat file_stat;
  std::string link_path;

  error = GetAttrEx(path, &file_stat, link_path);

  // ディレクトリの場合
  if (error >= 0 && S_ISDIR(file_stat.st_mode)) {
    // すべてのサーバーにリネーム命令を実行する
    BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {

#ifdef USE_SESSION_POOL_FOR_IO
      msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
      msgpack::rpc::client c(info.host, info.port);
#endif

      MSGPACK_CLIENT_CALL(
          error = c.call(CODE(kSetXAttr), std::string(path), std::string(name), std::string(value), size, flags).get<Error>();
      );
    }

  } else {
    std::string bb_host;
    uint16_t bb_port;

    Error error = GetBurstBuffer(path, &bb_host, &bb_port);
    if (error != kCBBSuccess)
      return error;

#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
    msgpack::rpc::client c(bb_host, bb_port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kSetXAttr), std::string(path), std::string(name), std::string(value), size, flags).get<Error>();
    );
  }

  return error;
}

/**
 * @breaf 拡張ファイル属性取得
 * @param path ファイルパス
 * @param name 名前
 * @param value 設定値
 * @param size サイズ
 * @return Error値
 */
Error BurstBufferClient::GetXAttr(const char *path, const char *name, char *value, size_t size) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  typedef msgpack::type::tuple<size_t, std::string> Result;
  MSGPACK_CLIENT_CALL(
      Result result = c.call(CODE(kGetXAttr), std::string(path), std::string(name), std::string(value != NULL ? value : ""), size).get<Result>();
      error = result.get<0>();
      std::string value_result = result.get<1>();
      if (value != NULL) {
        strcpy(value, value_result.c_str());
      }
  );

  //return error;
  return kCBBSuccess;
}

/**
 * @breaf リスト属性
 * @param path ファイルパス
 * @param list リスト
 * @param size サイズ
 * @return Error値
 */
Error BurstBufferClient::ListXAttr(const char *path, char *list, size_t size) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  typedef msgpack::type::tuple<size_t, std::string> Result;
  MSGPACK_CLIENT_CALL(
      Result result = c.call(CODE(kListXAttr), std::string(path), std::string(list != NULL ? list: ""), size).get<Result>();
      error = result.get<0>();
      std::string list_result = result.get<1>();
      if (list != NULL) {
        strcpy(list, list_result.c_str());
      }
  );

  //return error;
  return kCBBSuccess;
}

/**
 * @breaf 拡張ファイル属性削除
 * @param path ファイルパス
 * @param name 名前
 * @return Error値
 */
Error BurstBufferClient::RemoveXAttr(const char *path, const char *name) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kRemoveXAttr), std::string(path), std::string(name)).get<Error>();
  );

  return error;
}

/**
 * @breaf 初期化
 * @param config_path 設定ファイルパス
 * @return Error値
 */
Error BurstBufferClient::Init(const char *config_path) {
  if (!settings_.Load(config_path, false)) {
    return kCBBUnknownError;
  }

  select_server_.Init(settings_, new cbb::HashCalcMD5());
  g_fuse_mutex.Init();

  BOOST_FOREACH(std::string host, settings_.client_hosts()) {
    DMSG("host = %s\n", host.c_str());
  }
  DMSG("port = %d\n", settings_.client_port());
  DMSG("------------------------\n");

  life_.reset(new msgpack::zone());
  return static_cast<Error>(0);
}

/**
 * @breaf 破棄
 * @return Error値
 */
Error BurstBufferClient::Destroy() {
  Thread::Release();
  return kCBBSuccess;
}

/**
 * @breaf ファイルアクセス
 * @param path ファイルパス
 * @param mode モード値
 * @return Error値
 */
Error BurstBufferClient::Access(const char *path, int mode) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kAccess), std::string(path), mode).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイルクリエイト
 * @param path ファイルパス
 * @param flags フラグ
 * @param mode モード値
 * @param file_ptr ファイル情報ポインタ
 * @return Error値
 */
Error BurstBufferClient::Create(const char *path, int flags,  mode_t mode, File *file_ptr) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

  file_ptr->path = path;
  file_ptr->bb_host = bb_host;
  file_ptr->bb_port = bb_port;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  int fd = -1;
  MSGPACK_CLIENT_CALL(
      fd = c.call(CODE(kCreate), std::string(path), flags, mode).get<int>();
  );

  if (fd < 0)
    return static_cast<Error>(fd);

  file_ptr->fd_org = fd;
  file_ptr->fd = ((uint64_t)addr_to_binary(bb_host.c_str()) << 32) | fd;

  return kCBBSuccess;
}

/**
 * @breaf ファイルサイズ変更
 * @param file ファイル情報
 * @param size サイズ
 * @return Error値
 */
Error BurstBufferClient::FTruncate(const File &file, off_t size) {

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(file.bb_host, file.bb_port);
#else
  msgpack::rpc::client c(file.bb_host, file.bb_port);
#endif

  Error error = kCBBSuccess;

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kFTruncate), std::string(file.path), file.fd_org, size).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイル属性取得 (ファイルディスクリプタ版)
 * @param path ファイルパス
 * @param file_stat_ptr ファイルステータスポインタ
 * @param file ファイル情報
 * @return Error値
 */
Error BurstBufferClient::FGetAttr(const char *path, FileStat *file_stat_ptr, const File &file) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  typedef msgpack::type::tuple<Error, FileStat> Result;
  MSGPACK_CLIENT_CALL(
      Result result  = c.call(CODE(kFGetAttr), std::string(path), file.fd_org).get<Result>();
      error = result.get<0>();
      *file_stat_ptr = result.get<1>();
  );

  return error;
}

/**
 * @breaf ファイルロック
 * @param path ファイルパス
 * @param file ファイル情報
 * @param cmd コマンド
 * @param lockbuf ファイルロック情報
 * @return Error値
 */
Error BurstBufferClient::Lock(const char *path, const File &file, int cmd, struct flock *lockbuf) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  typedef msgpack::type::tuple<Error, FLock> Result;
  MSGPACK_CLIENT_CALL(
      Result result  = c.call(CODE(kLock), std::string(path), file.fd_org).get<Result>();
      error = result.get<0>();
      *lockbuf = result.get<1>();
  );

  return error;
}

/**
 * @breaf ファイル日時変更
 * @param path ファイルパス
 * @param times 日時情報
 * @return Error値
 */
Error BurstBufferClient::Utimens(const char *path, const TimeSpec times[2]) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kUtimens), std::string(path), times[0], times[1]).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイル先読み
 * @param path ファイルパス
 * @return Error値
 */
Error BurstBufferClient::FilePrevRead(const char *path) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kFilePrevRead), std::string(path)).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイルフラッシュ
 * @param path ファイルパス
 * @return Error値
 */
Error BurstBufferClient::FileFlush(const char *path) {
  std::string bb_host;
  uint16_t bb_port;

  Error error = GetBurstBuffer(path, &bb_host, &bb_port);
  if (error != kCBBSuccess)
    return error;

#ifdef USE_SESSION_POOL_FOR_IO
  msgpack::rpc::session c = session_pool_.get_session(bb_host, bb_port);
#else
  msgpack::rpc::client c(bb_host, bb_port);
#endif

  MSGPACK_CLIENT_CALL(
      error = c.call(CODE(kFileFlush), std::string(path)).get<Error>();
  );

  return error;
}

/**
 * @breaf ファイルフラッシュ
 * @param path ファイルパス
 * @return Error値
 */
Error BurstBufferClient::LocalFileExport() {
  Error error;

  BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {
#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
    msgpack::rpc::client c(info.host, info.port);
#endif

    MSGPACK_CLIENT_CALL(
        error = c.call(CODE(kLocalFileExport)).get<Error>();
    );
  }

  return error;
}

/**
 * @see Thread::ThreadCall
 * @breaf 先読みファイルのコピー
 * @return 呼び出しを継続するかどうか
 */
bool BurstBufferClient::ThreadCall(void *user_data) {
  FileStats dir_files;
  Error error;
  std::string host;
  uint16_t port;

  dir_files.clear();

  BOOST_FOREACH(ServerInfo info, select_server_.server_list()) {
#ifdef USE_SESSION_POOL_FOR_IO
    msgpack::rpc::session c = session_pool_.get_session(info.host, info.port);
#else
    msgpack::rpc::client c(info.host, info.port);
#endif

    FileStats file_stats;

    typedef msgpack::type::tuple<Error, FileStats> Result;
    MSGPACK_CLIENT_CALL(
        Result result = c.call(CODE(kReadDir), std::string(prev_read_dir_.c_str()), (off_t)0, (int)kDirSecondary).get<Result>();
        error = result.get<0>();
        file_stats = result.get<1>();
    );

    dir_files.insert(file_stats.begin(), file_stats.end());
  }

  for (FileStats::iterator it = dir_files.begin(); it != dir_files.end(); it++) {
    std::string filename = it->first;
    std::string path = prev_read_dir_ + filename;

//DMSG("FilePrevRead path = %s \n", path.c_str());

    if (filename == "." || filename == ".." || filename == target_filename_)
      continue;

    error = FilePrevRead(path.c_str());
  }

  return false;
}

/**
 * @breaf BurstBufferサーバー取得
 * @param path ファイルパス
 * @param bb_host_ptr ホスト情報
 * @param bb_port_ptr 接続ポート番号
 * @return Error値
 */
Error BurstBufferClient::GetBurstBuffer(const char *path, std::string *bb_host_ptr, uint16_t *bb_port_ptr) {

  std::string host = "";
  int port = 0;

  select_server_.GetInfo(path, host, port);

  // Should introduce schedule feature here.
  *bb_host_ptr = host;
  *bb_port_ptr = port;

  return kCBBSuccess;
}

/**
 * @breaf ファイル先読み開始
 * @param path ファイルパス
 */
void BurstBufferClient::StartPrevFileRead(const char* path) {
  boost::filesystem::path fpath(path);
  prev_read_dir_ = fpath.parent_path().c_str();
  target_filename_ = fpath.filename().c_str();
  Thread::Create((void *)NULL);
}

} // namespace cbb

ext: cc

max_stars_repo_name: EBD-CREST/cbb

max_issues_repo_name: EBD-CREST/cbb

max_forks_repo_name: EBD-CREST/cbb


text:
// Copyright Abel Sinkovics (abel@sinkovics.hu) 2010.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <mpllibs/metamonad/do_.hpp>
#include <mpllibs/metamonad/lazy_metafunction.hpp>
#include <mpllibs/metamonad/either.hpp>
#include <mpllibs/metamonad/syntax.hpp>

#include <boost/test/unit_test.hpp>

#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/tag.hpp>
#include <boost/mpl/assert.hpp>

#include "common.hpp"

using boost::mpl::minus;

using mpllibs::metamonad::right;

namespace
{
  MPLLIBS_LAZY_METAFUNCTION(minus_2, (A)) ((right<minus<A, int2> >));
}

BOOST_AUTO_TEST_CASE(test_do1)
{
  using mpllibs::metamonad::do_;
  using mpllibs::metamonad::syntax;
  using mpllibs::metamonad::set;
  using mpllibs::metamonad::do_return;

  using boost::mpl::equal_to;

  typedef boost::mpl::tag<int13>::type int_tag;
  typedef mpllibs::metamonad::either_tag<int_tag, int_tag> either;

  // test_do
  BOOST_MPL_ASSERT((
    equal_to<
      right<int11>,
      do_<either,
        syntax<set<x, do_return<int13> > >,
        syntax<minus_2<x> >
      >::type
    >
  ));

  // test_do_three_steps
  BOOST_MPL_ASSERT((
    equal_to<
      right<int9>,
      do_<either,
        syntax<set<x, do_return<int13> > >,
        syntax<set<y, minus_2<x> > >,
        syntax<minus_2<y> >
      >::type
    >
  ));

  // test_do_two_calls
  BOOST_MPL_ASSERT((
    equal_to<
      right<int9>,
      do_<either,
        syntax<set<x, do_return<int13> > >,
        syntax<set<y, minus_2<x> > >,
        syntax<minus_2<x> >,
        syntax<minus_2<y> >
      >::type
    >
  ));

  // test_do_two_returns
  BOOST_MPL_ASSERT((
    equal_to<
      right<int13>,
      do_<either,
        syntax<do_return<int11> >,
        syntax<do_return<int13> >
      >::type
    >
  ));

  // test_nested_do_with_return
  BOOST_MPL_ASSERT((
    equal_to<
      right<right<int13> >,
      do_<either,
        syntax<
          do_return<
            do_<either,
              syntax<do_return<int13> >
            >
          >
        >
      >::type
    >
  ));
}

ext: cpp


text:
#include "FBXImporter.hpp"

#include <unordered_set>

#include <fbxsdk.h>
#include <fbxsdk/utils/fbxgeometryconverter.h>
#include <boost/algorithm/string/case_conv.hpp>

#include <FishEngine/Debug.hpp>
#include <FishEngine/GameObject.hpp>
#include <FishEngine/MeshFilter.hpp>
#include <FishEngine/MeshRenderer.hpp>
#include <FishEngine/SkinnedMeshRenderer.hpp>
#include <FishEngine/Texture.hpp>
#include <FishEngine/Texture2D.hpp>
#include <FishEngine/Application.hpp>

#include "AssetDataBase.hpp"
#include "FBXImporter/RawMesh.hpp"

//#include <Animation/AnimationUtility.hpp>
//#include <Animation/AnimationClip.hpp>
////#include <Animation/AnimationClipInfo.hpp>
//#include <Animation/AnimationSplitInfo.hpp>

#include <FishEngine/Animation.hpp>
#include <FishEngine/AnimationClip.hpp>
#include <FishEngine/Animation/AnimationCurve.hpp>
#include <FishEngine/Animation/AnimationCurveUtility.hpp>

using namespace FishEngine;
using namespace FishEditor;

Matrix4x4 FBXToNativeType(fbxsdk::FbxAMatrix const & fmatrix)
{
	float f44[4][4];
	auto d44 = fmatrix.Double44();
	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			f44[i][j] = static_cast<float>(d44[j][i]);
		}
	}
	return Matrix4x4(f44);
}

Vector3 FBXToNativeType(const FbxVector4& value)
{
	Vector3 native;
	native.x = (float)value[0];
	native.y = (float)value[1];
	native.z = (float)value[2];

	return native;
}

Vector3 FBXToNativeType(const FbxDouble3& value)
{
	return Vector3(static_cast<float>(value[0]),
		static_cast<float>(value[1]),
		static_cast<float>(value[2]));
}

Vector2 FBXToNativeType(const FbxVector2& value)
{
	Vector2 native;
	native.x = (float)value[0];
	native.y = (float)value[1];

	return native;
}

Color FBXToNativeType(const FbxColor& value)
{
	Color native;
	native.r = (float)value[0];
	native.g = (float)value[1];
	native.b = (float)value[2];
	native.a = (float)value[3];

	return native;
}

FbxSurfaceMaterial* FBXToNativeType(FbxSurfaceMaterial* const& value)
{
	return value;
}

RotationOrder FBXToNativeType(EFbxRotationOrder order)
{
	RotationOrder rotationOrder;
	//EFbxRotationOrder order;
	//node->GetRotationOrder(FbxNode::eDestinationPivot, order);
	switch (order)
	{
		case eEulerXYZ:
			rotationOrder = RotationOrder::XYZ; break;
		case eEulerXZY:
			rotationOrder = RotationOrder::XZY; break;
		case eEulerYXZ:
			rotationOrder = RotationOrder::YXZ; break;
		case eEulerYZX:
			rotationOrder = RotationOrder::YZX; break;
		case eEulerZXY:
			rotationOrder = RotationOrder::ZXY; break;
		case eEulerZYX:
			rotationOrder = RotationOrder::ZYX; break;
		default: // do not support eSphericXYZ
			abort();
	}
	return rotationOrder;
}

int FBXToNativeType(const int & value)
{
	return value;
}

inline Vector3 FbxVector4ToVector3WithXFlipped(FbxVector4 const & v)
{
	return Vector3(static_cast<float>(v[0]),
				   static_cast<float>(v[1]),
				   static_cast<float>(v[2]) );
}

// skinned data
void FishEditor::FBXImporter::GetLinkData(FbxMesh* pMesh, MeshPtr mesh, std::map<uint32_t, uint32_t> const & vertexIndexRemapping)
{
	int lSkinCount = pMesh->GetDeformerCount(FbxDeformer::eSkin);
	if (lSkinCount <= 0)
	{
		mesh->m_skinned = false;
		return;
	}
	if (lSkinCount != 1)
	{
		// TODO: multiple skin
		abort();
	}
	mesh->m_skinned = true;

	int n_vertices = mesh->vertexCount();
	mesh->m_boneWeights.resize(n_vertices);

	//FbxCluster::ELinkMode lClusterMode = ((FbxSkin*)pMesh->GetDeformer(0, FbxDeformer::eSkin))->GetCluster(0)->GetLinkMode();

	FbxSkin * lSkinDeformer = (FbxSkin *)pMesh->GetDeformer(0, FbxDeformer::eSkin);
	//FbxSkin::EType lSkinningType = lSkinDeformer->GetSkinningType();

	// cluser == bone
	int lClusterCount = lSkinDeformer->GetClusterCount();
	std::vector<uint32_t> boneIndices(lClusterCount);

	mesh->m_boneNames.resize(lClusterCount);

	float scale = m_fileScale * m_globalScale;

	for (int lClusterIndex = 0; lClusterIndex != lClusterCount; ++lClusterIndex)
	{
		FbxCluster* lCluster= lSkinDeformer->GetCluster(lClusterIndex);
		int lIndexCount = lCluster->GetControlPointIndicesCount();
		int* lIndices = lCluster->GetControlPointIndices();
		double* lWeights = lCluster->GetControlPointWeights();

		auto & boneToIndex = m_model.m_avatar->m_boneToIndex;
		std::string boneName = (char *) lCluster->GetLink()->GetName();
		mesh->m_boneNames[lClusterIndex] = boneName;

		//fbxsdk::FbxAMatrix bindPoseMatrix;
		//lCluster->GetTransformLinkMatrix(bindPoseMatrix);	// this bind pose is in world(global) space
		////m_model.m_bindposes.push_back(FbxAMatrixToMatrix4x4(bindPoseMatrix));

		int boneId;
		auto it = boneToIndex.find(boneName);
		if ( it != boneToIndex.end() )
		{
			boneId = it->second;
		}
		else
		{
			// bone not in skeletion;
			abort();
			//boneId = m_boneCount;
			//m_model.m_avatar->m_boneToIndex[boneName] = boneId;
			//m_model.m_avatar->m_indexToBone[boneId] = boneName;
			////fbxsdk::FbxAMatrix transformMatrix;
			////lCluster->GetTransformMatrix(transformMatrix);
			//m_boneCount++;
		}

		fbxsdk::FbxAMatrix bindPoseMatrix;
		lCluster->GetTransformLinkMatrix(bindPoseMatrix);	// this bind pose is in world(global) space
		auto mat = FBXToNativeType(bindPoseMatrix);
		mat.m[0][3] *= scale;
		mat.m[1][3] *= scale;
		mat.m[2][3] *= scale;
		m_model.m_bindposes[boneId] = mat;

		boneIndices[lClusterIndex] = boneId;

		for ( int k = 0; k < lIndexCount; k++ )
		{
			int vertexId = lIndices[k];
			float weight = static_cast<float>(lWeights[k]);
			mesh->m_boneWeights[vertexId].AddBoneData(lClusterIndex, weight);
			auto it = vertexIndexRemapping.find(vertexId);
			while (it != vertexIndexRemapping.end())
			{
				vertexId = it->second;
				mesh->m_boneWeights[vertexId].AddBoneData(lClusterIndex, weight);
				it = vertexIndexRemapping.find(vertexId);
			}
		}
	}
	m_model.m_boneIndicesForEachMesh.emplace(mesh, std::move(boneIndices));
}

void FishEditor::FBXImporter::ImportSkeleton(fbxsdk::FbxScene* scene)
{
	std::deque<FbxNode*> todo;
	todo.push_back(scene->GetRootNode());
	while (!todo.empty())
	{
		FbxNode* node = todo.front();
		todo.pop_front();
		for (int i = 0; i < node->GetChildCount(); ++i)
		{
			auto child = node->GetChild(i);
			todo.push_back(child);
		}

		const char* nodeName = node->GetName();
		auto nodeAttributeCount = node->GetNodeAttributeCount();
		for (int i = 0; i < nodeAttributeCount; ++i)
		{
			auto nodeAttribute = node->GetNodeAttributeByIndex(i);
			auto type = nodeAttribute->GetAttributeType();
			if (type == FbxNodeAttribute::eSkeleton)
			{
				//FbxSkeleton* lSkeleton = (FbxSkeleton*)nodeAttribute;
				std::string boneName = nodeName;
				auto it = m_model.m_avatar->m_boneToIndex.find(boneName);
				if (it != m_model.m_avatar->m_boneToIndex.end())
				{
					// handle same name
					abort();
				}
				m_model.m_avatar->m_boneToIndex[boneName] = m_boneCount;
				m_model.m_avatar->m_indexToBone[m_boneCount] = boneName;
				m_boneCount++;
			}
		}
	}

	m_model.m_bindposes.resize(m_boneCount);
}

void FishEditor::FBXImporter::UpdateBones(FishEngine::TransformPtr const & node)
{
	auto boneName = node->gameObject()->name();
	auto & boneToIndex = m_model.m_avatar->m_boneToIndex;
	auto it = boneToIndex.find(boneName);
	if (it != boneToIndex.end())
	{
		int id = it->second;
		m_model.m_bones[id] = node;

		// world(global) space -> parent's local space
		if (node->parent() == nullptr)
		{
			node->setLocalToWorldMatrix(m_model.m_bindposes[id]);
		}
		else
		{
			auto l2w = node->parent()->worldToLocalMatrix() * m_model.m_bindposes[id];
			//bindPoses[id] = l2w.inverse();
			node->setLocalToWorldMatrix(l2w);
			m_model.m_bindposes[id] = node->worldToLocalMatrix();
		}
	}

	for (auto & child : node->children())
	{
		UpdateBones(child);
	}
}

MeshPtr FishEditor::FBXImporter::ParseMesh(FbxMesh* fbxMesh)
{
	assert(fbxMesh->IsTriangleMesh());

	auto it = m_model.m_fbxMeshLookup.find(fbxMesh);
	if (it != m_model.m_fbxMeshLookup.end())
	{
		auto meshIndex = it->second;
		return m_model.m_meshes[meshIndex];
	}

	fbxMesh->RemoveBadPolygons();
	fbxMesh->GenerateNormals(false, true, false);
	fbxMesh->GenerateTangentsDataForAllUVSets();
	//auto name1 = fbxMesh->GetName();
	//auto name2 = fbxMesh->GetNode()->GetName();

	// http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_mesh_html
	// A control point is an XYZ coordinate, it is synonym of vertex.
	// A polygon vertex is an index to a control point(the same control point can be referenced by multiple polygon vertices).
	// A polygon is a group of polygon vertices.The minimum valid number of polygon vertices to define a polygon is 3.

	int polygonCount = fbxMesh->GetPolygonCount();
	int vertexCount = fbxMesh->GetControlPointsCount();
	FbxVector4* controlPoints = fbxMesh->GetControlPoints();

//	auto attributeCount = fbxMesh->GetNode()->GetNodeAttributeCount();
//	std::vector<FbxMesh*> submeshes;
//	for (int attributeIndex = 1; attributeIndex < attributeCount; ++attributeIndex)
//	{
//		auto nodeAttribute = fbxMesh->GetNode()->GetNodeAttributeByIndex(attributeIndex);
//		if (nodeAttribute->GetAttributeType() == FbxNodeAttribute::eMesh)
//		{
//			FbxMesh* lMesh = (FbxMesh*)nodeAttribute;
//			submeshes.push_back(lMesh);
//			int submeshPolygonCount = lMesh->GetPolygonCount();
//			int submeshVertexCount = lMesh->GetControlPointsCount();
//		}
//	}

	if (fbxMesh->GetElementUVCount() == 0)
	{
		abort();
	}

	// get material info
	int lMaterialCount = fbxMesh->GetNode()->GetMaterialCount();
	if (lMaterialCount == 0)
	{
		// no material from fbx, set default material
		abort();
	}

	// use RawMesh to construct Mesh
	RawMesh rawMesh;

	rawMesh.SetFaceCount(polygonCount);
	rawMesh.SetVertexCount(vertexCount);

	int subMeshCount = lMaterialCount;
	int hasSubMesh = subMeshCount > 1;

	// TODO:
	// if this mesh only has one material, we assume this material applies to all polygons.
	// Better choice is to apply default material to polygons without materials.

	// use material info to split submeshes

	//std::vector<std::vector<int>> submeshPolygonIds;
	if (hasSubMesh)
	{
		rawMesh.m_subMeshCount = subMeshCount;
		rawMesh.m_submeshMap.resize(polygonCount);
		//rawMesh.m_submeshPolygonIds.resize(subMeshCount);
		//Debug::LogError("eByPolygon mapping material");
		//auto lMaterialElement = fbxMesh->GetElementMaterial(l);
		for (int l = 0; l < fbxMesh->GetElementMaterialCount(); l++)
		{
			auto lMaterialElement = fbxMesh->GetElementMaterial(l);
			if (lMaterialElement->GetMappingMode() == FbxLayerElement::eByPolygon)
			{
				//int lMatId = lMaterialElement->GetIndexArray().GetAt(0);
				//if (lMatId >= 0)
				//{
				//	auto lMaterial = fbxMesh->GetNode()->GetMaterial(lMatId);
				//	ParseMaterial(lMaterial);
				//}
				//else
				//{
				//	abort();
				//}

				if (lMaterialElement->GetReferenceMode() == FbxLayerElement::eIndex ||
					lMaterialElement->GetReferenceMode() == FbxLayerElement::eIndexToDirect)
				{
					int lIndexArrayCount = lMaterialElement->GetIndexArray().GetCount();
					if (lIndexArrayCount != polygonCount)
					{
						abort();
					}
					for (int i = 0; i < polygonCount; i++)
					{
						int lMatId = lMaterialElement->GetIndexArray().GetAt(i);
						rawMesh.m_submeshMap[i] = lMatId;
						//rawMesh.m_submeshPolygonIds[lMatId].push_back(i);
					}
				}
				else
				{
					abort();
				}
			}
			else
			{
				abort();
			}
		}
	}

	// positions
	for (int controlPointIndex = 0; controlPointIndex < vertexCount; ++controlPointIndex)
	{
		auto & p = controlPoints[controlPointIndex];
		float scale = m_fileScale * m_globalScale;
//		float x = -p[0] * scale;
//		float y = p[1] * scale;
//		float z = p[2] * scale;
		auto pp = FbxVector4ToVector3WithXFlipped(p);
		rawMesh.m_vertexPositions.emplace_back(pp * scale);
	}

	// indices(triangles)
	for (int polygonIndex = 0; polygonIndex < polygonCount; ++polygonIndex)
	{
		rawMesh.m_wedgeIndices.push_back( fbxMesh->GetPolygonVertex(polygonIndex, 0) );
		rawMesh.m_wedgeIndices.push_back( fbxMesh->GetPolygonVertex(polygonIndex, 1) );
		rawMesh.m_wedgeIndices.push_back( fbxMesh->GetPolygonVertex(polygonIndex, 2) );
	}

	int vertexId = 0;
	for (int i = 0; i < polygonCount; i++)	// for each triangle
	{
		int lPolygonSize = fbxMesh->GetPolygonSize(i); // should be 3
		assert(lPolygonSize == 3);
		for (int j = 0; j < lPolygonSize; j++)
		{
			int lControlPointIndex = fbxMesh->GetPolygonVertex(i, j);

			// UV
//			for (int l = 0; l < fbxMesh->GetElementUVCount(); ++l)
//			{
				FbxGeometryElementUV* leUV = fbxMesh->GetElementUV(0);
				int lTextureUVIndex = fbxMesh->GetTextureUVIndex(i, j);
				if (leUV->GetMappingMode() == FbxGeometryElement::eByPolygonVertex)
				{
					auto mode = leUV->GetReferenceMode();
					if (mode == FbxGeometryElement::eDirect || mode == FbxGeometryElement::eIndexToDirect)
					{
						auto uv = leUV->GetDirectArray().GetAt(lTextureUVIndex);
						rawMesh.m_wedgeTexCoords.emplace_back((float)uv[0], (float)uv[1]);
					}
					else
					{
						abort();
					}
				}
				else
				{
					abort();
				}
//			}

			// Normal
			//for (int l = 0; l < fbxMesh->GetElementNormalCount(); ++l)
			//{
				FbxGeometryElementNormal* leNormal = fbxMesh->GetElementNormal(0);
				//FBXSDK_sprintf(header, 100, "            Normal: ");

				if (leNormal->GetMappingMode() == FbxGeometryElement::eByPolygonVertex)
				{
					auto mode = leNormal->GetReferenceMode();
					if (mode == FbxGeometryElement::eDirect)
					{
						//Display3DVector(header, leNormal->GetDirectArray().GetAt(vertexId));
						auto n = leNormal->GetDirectArray().GetAt(vertexId);
						rawMesh.m_wedgeNormals.emplace_back(FbxVector4ToVector3WithXFlipped(n));
					}
					else if (mode == FbxGeometryElement::eIndexToDirect)
					{
						int id = leNormal->GetIndexArray().GetAt(vertexId);
						//Display3DVector(header, leNormal->GetDirectArray().GetAt(id));
						auto n = leNormal->GetDirectArray().GetAt(id);
						rawMesh.m_wedgeNormals.emplace_back(FbxVector4ToVector3WithXFlipped(n));
					}
					else
					{
						abort();
					}
				}
				else if (leNormal->GetMappingMode() == FbxGeometryElement::eByControlPoint)
				{
					auto mode = leNormal->GetReferenceMode();
					if (mode == FbxGeometryElement::eDirect)
					{
						auto n = leNormal->GetDirectArray().GetAt(lControlPointIndex);
						rawMesh.m_wedgeNormals.emplace_back(FbxVector4ToVector3WithXFlipped(n));
					}
					else if (mode == FbxGeometryElement::eIndexToDirect)
					{
						int id = leNormal->GetIndexArray().GetAt(lControlPointIndex);
						auto n = leNormal->GetDirectArray().GetAt(id);
						rawMesh.m_wedgeNormals.emplace_back(FbxVector4ToVector3WithXFlipped(n));
					}
					else
					{
						abort();
					}
				}
				else
				{
					abort();
				}
			//}

			// Tangent
			//for (int l = 0; l < fbxMesh->GetElementTangentCount(); ++l)
			//{
				FbxGeometryElementTangent* leTangent = fbxMesh->GetElementTangent(0);
				//FBXSDK_sprintf(header, 100, "            Tangent: ");

				if (leTangent->GetMappingMode() == FbxGeometryElement::eByPolygonVertex)
				{
					auto mode = leTangent->GetReferenceMode();
					if (mode == FbxGeometryElement::eDirect)
					{
						//Display3DVector(header, leTangent->GetDirectArray().GetAt(vertexId));
						auto t = leTangent->GetDirectArray().GetAt(vertexId);
						rawMesh.m_wedgeTangents.emplace_back(FbxVector4ToVector3WithXFlipped(t));
					}
					else if (mode == FbxGeometryElement::eIndexToDirect)
					{
						int id = leTangent->GetIndexArray().GetAt(vertexId);
						//Display3DVector(header, leTangent->GetDirectArray().GetAt(id));
						auto t = leTangent->GetDirectArray().GetAt(id);
						rawMesh.m_wedgeTangents.emplace_back(FbxVector4ToVector3WithXFlipped(t));
					}
					else
					{
						abort();
					}
				}
				else if (leTangent->GetMappingMode() == FbxGeometryElement::eByControlPoint)
				{
					auto mode = leTangent->GetReferenceMode();
					if (mode == FbxGeometryElement::eDirect)
					{
						auto t = leTangent->GetDirectArray().GetAt(lControlPointIndex);
						rawMesh.m_wedgeTangents.emplace_back(FbxVector4ToVector3WithXFlipped(t));
					}
					else if (mode == FbxGeometryElement::eIndexToDirect)
					{
						int id = leTangent->GetIndexArray().GetAt(lControlPointIndex);
						auto t = leTangent->GetDirectArray().GetAt(id);
						rawMesh.m_wedgeTangents.emplace_back(FbxVector4ToVector3WithXFlipped(t));
					}
					else
					{
						abort();
					}
				}
				else
				{
					abort();
				}
			//}

#if 0
			// Binormal
			for (int l = 0; l < fbxMesh->GetElementBinormalCount(); ++l)
			{

				FbxGeometryElementBinormal* leBinormal = fbxMesh->GetElementBinormal(l);

				//FBXSDK_sprintf(header, 100, "            Binormal: ");
				if (leBinormal->GetMappingMode() == FbxGeometryElement::eByPolygonVertex)
				{
					switch (leBinormal->GetReferenceMode())
					{
					case FbxGeometryElement::eDirect:
						//Display3DVector(header, leBinormal->GetDirectArray().GetAt(vertexId));
						break;
					case FbxGeometryElement::eIndexToDirect:
					{
						int id = leBinormal->GetIndexArray().GetAt(vertexId);
						//Display3DVector(header, leBinormal->GetDirectArray().GetAt(id));
					}
					break;
					default:
						break; // other reference modes not shown here!
					}
				}
			}
#endif
			vertexId++;
		} // for polygonSize
	} // for polygonCount

	auto mesh = rawMesh.ToMesh();
	GetLinkData(fbxMesh, mesh, rawMesh.m_vertexIndexRemapping);

	m_model.m_fbxMeshLookup[fbxMesh] = m_model.m_meshes.size();
	m_model.m_meshes.push_back(mesh);

	return mesh;
}

FishEngine::MaterialPtr FishEditor::FBXImporter::ParseMaterial(fbxsdk::FbxSurfaceMaterial * pMaterial)
{
	auto it = m_model.m_fbxMaterialLookup.find(pMaterial);
	if (it != m_model.m_fbxMaterialLookup.end())
	{
		// already parsed
		return m_model.m_materials[it->second];
	}

	FbxProperty lProperty;
	//Diffuse Textures
	lProperty = pMaterial->FindProperty(FbxSurfaceMaterial::sDiffuse);
	//DisplayTextureNames(lProperty, lConnectionString);

	std::string diffuseTexturePath;
	int lNbTextures = lProperty.GetSrcObjectCount<FbxTexture>();
	for (int j = 0; j < lNbTextures; ++j)
	{
		FbxTexture* lTexture = lProperty.GetSrcObject<FbxTexture>(j);
		if (lTexture)
		{
			std::string textureName = lTexture->GetName();

			auto * lFileTexture = FbxCast<FbxFileTexture>(lTexture);
			if (lFileTexture == nullptr)
			{
				// do not support non-file texture
				//abort();
				continue;
			}
			diffuseTexturePath = lFileTexture->GetFileName();
			//Debug::LogWarning("diffuse texture: %s", texturePath.c_str());
			break;
		}
	}

	MaterialPtr ret_material = Material::defaultMaterial();
	if (!diffuseTexturePath.empty())
	{
		auto textureName = Path(diffuseTexturePath).filename();
//		auto texturePath = Application::dataPath() / "textures" / textureName;
//		auto diffuseTexture = AssetDatabase::LoadAssetAtPath2<Texture>(texturePath);
		auto diffuseTexture = AssetDatabase::FindAssetByFilename<Texture>(textureName.string());
		ret_material = Material::InstantiateBuiltinMaterial("Diffuse");
		ret_material->setName(textureName.string());
		if (diffuseTexture != nullptr)
		{
			ret_material->setMainTexture(diffuseTexture);
		}
		else
		{
			LogWarning("Texture not found: " + textureName.string());
			ret_material->setMainTexture(Texture2D::whiteTexture());
		}
	}
	m_model.m_fbxMaterialLookup[pMaterial] = m_model.m_materials.size();
	m_model.m_materials.push_back(ret_material);
	return ret_material;
}

void FishEditor::FBXImporter::BakeTransforms(FbxScene * scene)
{
	// FBX stores transforms in a more complex way than just translation-rotation-scale as used by Banshee.
	// Instead they also support rotations offsets and pivots, scaling pivots and more. We wish to bake all this data
	// into a standard transform so we can access it using node's local TRS properties (e.g. FbxNode::LclTranslation).

	// fbx sdk doc:
	// http://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_C35D98CB_5148_4B46_82D1_51077D8970EE_htm

	double frameRate = FbxTime::GetFrameRate(scene->GetGlobalSettings().GetTimeMode());

	std::deque<FbxNode*> todo;
	todo.push_back(scene->GetRootNode());

	while (todo.size() > 0)
	{
		FbxNode* node = todo.front();
		todo.pop_front();

		FbxVector4 zero(0, 0, 0);
		FbxVector4 one(1, 1, 1);

		// Activate pivot converting
		node->SetPivotState(FbxNode::eSourcePivot, FbxNode::ePivotActive);
		node->SetPivotState(FbxNode::eDestinationPivot, FbxNode::ePivotActive);

		// We want to set all these to 0 (1 for scale) and bake them into the transforms
		node->SetPostRotation(FbxNode::eDestinationPivot, zero);
		node->SetPreRotation(FbxNode::eDestinationPivot, zero);
		node->SetRotationOffset(FbxNode::eDestinationPivot, zero);
		node->SetScalingOffset(FbxNode::eDestinationPivot, zero);
		node->SetRotationPivot(FbxNode::eDestinationPivot, zero);
		node->SetScalingPivot(FbxNode::eDestinationPivot, zero);
		node->SetGeometricTranslation(FbxNode::eDestinationPivot, zero);
		node->SetGeometricRotation(FbxNode::eDestinationPivot, zero);
		node->SetGeometricScaling(FbxNode::eDestinationPivot, one);

		// FishEngine assumes euler angles are in ZXY order
		// do not use this line, since it will mess up animation curves
		//node->SetRotationOrder(FbxNode::eDestinationPivot, eEulerZXY);

		EFbxRotationOrder lRotationOrder;
		node->GetRotationOrder(FbxNode::eSourcePivot , lRotationOrder);
		node->SetRotationOrder(FbxNode::eDestinationPivot , lRotationOrder);

		// Keep interpolation as is
		node->SetQuaternionInterpolation(FbxNode::eDestinationPivot, node->GetQuaternionInterpolation(FbxNode::eSourcePivot));

		for (int i = 0; i < node->GetChildCount(); i++)
		{
			FbxNode* childNode = node->GetChild(i);
			todo.push_back(childNode);
		}
	}

	scene->GetRootNode()->ConvertPivotAnimationRecursive(nullptr, FbxNode::eDestinationPivot, frameRate);
	//scene->GetRootNode()->ResetPivotSetAndConvertAnimation();
}

/**
* Print a node, its attributes, and all its children recursively.
*/
GameObjectPtr FishEditor::FBXImporter::ParseNodeRecursively(FbxNode* pNode)
{
	const char* nodeName = pNode->GetName();
	auto go = GameObject::Create();
	go->setName(nodeName);
	go->setPrefabInternal(m_model.m_modelPrefab);
	go->transform()->setPrefabInternal(m_model.m_modelPrefab);

	FbxDouble3 t = pNode->LclTranslation.Get();
	FbxDouble3 r = pNode->LclRotation.Get();
	FbxDouble3 s = pNode->LclScaling.Get();

	float scale = m_fileScale * m_globalScale;
	go->transform()->setLocalPosition(t[0] * scale, t[1] * scale, t[2] * scale);
	go->transform()->setLocalScale(s[0], s[1], s[2]);

	EFbxRotationOrder rotationOrder;
	pNode->GetRotationOrder(FbxNode::eSourcePivot, rotationOrder);
	RotationOrder order = FBXToNativeType(rotationOrder);
	Quaternion rot = Quaternion::Euler(order, r[0], r[1], r[2]);
	go->transform()->setLocalRotation(rot);

	m_model.m_fbxNodeLookup[pNode] = go->transform();
	FishEditor::AssetDatabase::s_allAssetObjects.insert(go);

	auto nodeAttributeCount = pNode->GetNodeAttributeCount();
	for (int i = 0; i < nodeAttributeCount; ++i)
	{
		auto nodeAttribute = pNode->GetNodeAttributeByIndex(i);
		auto type = nodeAttribute->GetAttributeType();
		if (type == FbxNodeAttribute::eMesh)
		{
			FbxMesh* lMesh = (FbxMesh*)nodeAttribute;
			auto mesh = ParseMesh(lMesh);
			m_model.m_meshes.push_back(mesh);
			if (mesh->name().empty())
			{
				mesh->setName(nodeName);
			}

			if (IsNewlyCreated())
			{
				m_recycleNameToFileID[mesh->name()] = m_nextMeshFileID;
				m_fileIDToRecycleName[m_nextMeshFileID] = mesh->name();
				m_nextMeshFileID += 2;
			}

			int lMaterialCount = pNode->GetMaterialCount();
			MaterialPtr material;
			if (lMaterialCount == 0)
			{
				material = Material::defaultMaterial();
			}
			else
			{
				auto lMaterial = pNode->GetMaterial(0);
				material = ParseMaterial(lMaterial);
			}

			RendererPtr renderer;
			if (mesh->m_skinned)
			{
				auto srenderer = go->AddComponent<SkinnedMeshRenderer>();
				m_model.m_skinnedMeshRenderers.push_back(srenderer);
				srenderer->SetMaterial(material);
				srenderer->setSharedMesh(mesh);
				srenderer->setAvatar(m_model.m_avatar);
				srenderer->setRootBone(m_model.m_rootNode->transform());
				renderer = srenderer;
			}
			else
			{
				go->AddComponent<MeshFilter>()->SetMesh(mesh);
				renderer = go->AddComponent<MeshRenderer>();
				renderer->SetMaterial(material);
			}

			for (int i = 1; i < lMaterialCount; ++i)
			{
				auto lMaterial = pNode->GetMaterial(i);
				material = ParseMaterial(lMaterial);
				renderer->AddMaterial(material);
			}
		}
	}

	// Recursively print the children.
	for (int j = 0; j < pNode->GetChildCount(); j++)
	{
		auto child = ParseNodeRecursively(pNode->GetChild(j));
		child->transform()->SetParent(go->transform(), false);
	}

	//boost::algorithm::is_iless compare;

	go->transform()->children().sort([](std::weak_ptr<Transform> const & lhs, std::weak_ptr<Transform> const & rhs) {
		return boost::algorithm::to_lower_copy(lhs.lock()->name()) < boost::algorithm::to_lower_copy(rhs.lock()->name());
	});

	return go;
}

void FishEditor::FBXImporter::ImportTo(FishEngine::GameObjectPtr & model)
{
	abort();
}

void FishEditor::FBXImporter::Reimport()
{
	Load(m_assetPath);
}

PrefabPtr FishEditor::FBXImporter::Load(FishEngine::Path const & path)
{
	if (m_model.m_modelPrefab == nullptr)
	{
		m_model.m_modelPrefab = MakeShared<Prefab>();
		m_model.m_modelPrefab->setIsPrefabParent(true);
	}
	if (m_model.m_avatar == nullptr)
	{
		m_model.m_avatar = MakeShared<Avatar>();
	}

	// http://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_29C09995_47A9_4B49_9535_2F6BDC5C4107_htm

	// Initialize the SDK manager. This object handles memory management.
	FbxManager * lSdkManager = FbxManager::Create();

	// Create the IO settings object.
	FbxIOSettings * ios = FbxIOSettings::Create(lSdkManager, IOSROOT);
	lSdkManager->SetIOSettings(ios);

	// Create an importer using the SDK manager.
	FbxImporter* lImporter = FbxImporter::Create(lSdkManager, "");

	// Use the first argument as the filename for the importer.
	if (!lImporter->Initialize(path.string().c_str(), -1, lSdkManager->GetIOSettings()))
	{
		LogError("Call to FbxImporter::Initialize() failed.");
		LogError(std::string( "Error returned: ") + lImporter->GetStatus().GetErrorString() );
		abort();
	}

	auto name = path.stem().string();

	// Create a new scene so that it can be populated by the imported file.
	FbxScene * lScene = FbxScene::Create(lSdkManager, name.c_str());

	// Import the contents of the file into the scene.
	lImporter->Import(lScene);

	// The file is imported, so get rid of the importer.
	lImporter->Destroy();

	//FbxAxisSystem::MayaYUp.ConvertScene(lScene);
	//FbxAxisSystem::DirectX.ConvertScene(lScene); // wrong!
	// FbxSystemUnit::m.ConvertScene(lScene);
	// http://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_CC93340E_C4A1_49EE_B048_E898F856CFBF_htm
	// do NOT use FbxSystemUnit::ConvertScene(lScene), which just simply set transform.scale of root nodes.
	if ( lScene->GetGlobalSettings().GetSystemUnit() == FbxSystemUnit::mm )
	{
		m_fileScale = 0.001f;
	}
	else if ( lScene->GetGlobalSettings().GetSystemUnit() == FbxSystemUnit::dm)
	{
		m_fileScale = 0.1f;
	}
	else if (lScene->GetGlobalSettings().GetSystemUnit() == FbxSystemUnit::m)
	{
		m_fileScale = 1.0f;
	}
	else if (lScene->GetGlobalSettings().GetSystemUnit() == FbxSystemUnit::cm)
	{
		m_fileScale = 0.01f;
	}
	else
	{
		abort();
	}
//	auto sceneSystemUnit = lScene->GetGlobalSettings().GetSystemUnit();
//	if (sceneSystemUnit.GetScaleFactor() != 1.0f)
//	{
//		FbxSystemUnit::m.ConvertScene(lScene);
//	}

	FbxGeometryConverter converter(lSdkManager);
	converter.Triangulate(lScene, true);
	//converter.SplitMeshesPerMaterial(lScene, true);

	BakeTransforms(lScene);

	ImportSkeleton(lScene);

	// Print the nodes of the scene and their attributes recursively.
	// Note that we are not printing the root node because it should
	// not contain any attributes.
	FbxNode* lRootNode = lScene->GetRootNode();
	auto & root = m_model.m_rootNode;
	root = FishEngine::GameObject::Create();
	root->setName(name);
	root->setPrefabInternal(m_model.m_modelPrefab);
	root->transform()->setPrefabInternal(m_model.m_modelPrefab);
	AssetDatabase::s_allAssetObjects.insert(root);
	if (lRootNode)
	{
		for (int i = 0; i < lRootNode->GetChildCount(); i++)
		{
			auto child = ParseNodeRecursively(lRootNode->GetChild(i));
			child->transform()->SetParent(root->transform(), false);
		}
	}

	ImportAnimations(lScene);

	// Destroy the SDK manager and all the other objects it was handling.
	lSdkManager->Destroy();

	m_model.m_bones.resize(m_boneCount);
	UpdateBones(root->transform());

	for (auto & renderer : m_model.m_skinnedMeshRenderers)
	{
		auto mesh = renderer->sharedMesh();
		renderer->bones().reserve(mesh->m_boneNames.size());
		//mesh->m_bindposes = m_model.m_bindposes;
		for (auto & boneName : mesh->m_boneNames)
		{
			int boneId = m_model.m_avatar->m_boneToIndex[boneName];
			renderer->bones().push_back(m_model.m_bones[boneId]);
		}
	}

	for (auto & mesh : m_model.m_meshes)
	{
		AssetImporter::s_objectInstanceIDToPath[mesh->GetInstanceID()] = path;
		if (mesh->m_skinned)
		{
			mesh->m_bindposes.reserve(mesh->m_boneNames.size());
			//mesh->m_bones.reserve(mesh->m_boneNames.size());
			//mesh->m_bindposes = m_model.m_bindposes;
			for (auto & boneName : mesh->m_boneNames)
			{
				int boneId = m_model.m_avatar->m_boneToIndex[boneName];
				mesh->m_bindposes.push_back(m_model.m_bindposes[boneId]);
				//mesh->m_bones.push_back(m_model.m_bones[boneId]);
			}

			// make sure all the weights sum to 1.
			for (auto & bw : mesh->m_boneWeights)
			{
				if (bw.weight[0] > 0)
				{
					float boneWeightScale = 0.0f;
					for (float w : bw.weight)
					{
						boneWeightScale += w;
					}
					if ( Mathf::Abs(boneWeightScale - 1.0f) > 1E-5f)
					{
						boneWeightScale = 1.0f / boneWeightScale;
						for (float & w : bw.weight)
						{
							w *= boneWeightScale;
						}
					}
				}
			}
		}
	}

	//std::deque<TransformPtr> transforms;
	//transforms.push_back(m_model.m_rootNode->transform());
	//while (!transforms.empty())
	//{
	//	auto current = transforms.front();
	//	transforms.pop_front();
	//	for (auto & child : current->children())
	//	{
	//		transforms.push_back(child.lock());
	//	}
	//
	//	auto srenderer = current->gameObject()->GetComponent<SkinnedMeshRenderer>();
	//	if (srenderer != nullptr)
	//	{
	//		srenderer.
	//	}
	//}

	//for (int i = 0; i < m_boneCount; ++i)
	//{
	//	Debug::LogWarning("%s", m_model.m_avatar->m_indexToBone[i].c_str());
	//	PrintMatrix(m_bindPoses[i]);
	//}

	//m_model.m_rootNode = root;
	if (m_model.m_animationClips.size() > 0)
	{
		auto animation = root->AddComponent<Animation>();
		animation->m_clip = m_model.m_animationClips.front();
	}

	m_model.m_modelPrefab->setRootGameObject(root);

	if (IsNewlyCreated())
	{
		BuildFileIDToRecycleName();
	}

	m_asset->Add(m_model.m_modelPrefab->rootGameObject());
	for (auto & mesh : m_model.m_meshes)
		m_asset->Add(mesh);

	for (auto & clip : m_model.m_animationClips)
		m_asset->Add(clip);
	m_asset->Add(m_model.m_avatar);

	return m_model.m_modelPrefab;
}

int IncreaseMapValue(std::map<int, int> & dict, int key)
{
	auto it = dict.find(key);
	if (it == dict.end())
	{
		dict[key] = 1;
		return 0;
	}
	int ret = it->second;
	it->second++;
	return ret;
}

void FishEditor::FBXImporter::RecursivelyBuildFileIDToRecycleName(FishEngine::TransformPtr const & transform)
{
	auto gameObject = transform->gameObject();
	{
		constexpr int classID = FishEngine::ClassID<GameObject>();
		int id = IncreaseMapValue(m_classIDCount, classID);
		id = classID * 100000 + id*2;
		m_fileIDToRecycleName[id] = gameObject->name();
	}
	{
		constexpr int classID = FishEngine::ClassID<Transform>();
		int id = IncreaseMapValue(m_classIDCount, classID);
		id = classID * 100000 + id*2;
		m_fileIDToRecycleName[id] = gameObject->name();
	}

	for (auto & component : gameObject->Components())
	{
		int classID = component->ClassID();
		int id = IncreaseMapValue(m_classIDCount, classID);
		id = classID * 100000 + id*2;
		m_fileIDToRecycleName[id] = component->name();
	}

	for (auto & child : transform->children())
	{
		RecursivelyBuildFileIDToRecycleName(child);
	}
}

void FishEditor::FBXImporter::BuildFileIDToRecycleName()
{
	RecursivelyBuildFileIDToRecycleName(m_model.m_modelPrefab->rootGameObject()->transform());
}

AnimationClipPtr FishEditor::FBXImporter::ConvertAnimationClip(const FBXAnimationClip& fbxClip)
{
	auto result = MakeShared<AnimationClip>();
	result->setName(fbxClip.name);
	result->frameRate = fbxClip.sampleRate;
	result->length = fbxClip.end - fbxClip.start;

	auto IsBone = [this](std::string const& name) {
		auto it = m_model.m_avatar->m_boneToIndex.find(name);
		return it != m_model.m_avatar->m_boneToIndex.end();
	};

	for (auto & animation : fbxClip.boneAnimations)
	{
		// TODO: move
		//std::string path = "";
		auto node = animation.node;
		std::string path = node->name();
		node = node->parent();
		while (node != nullptr)
		{
			if (!IsBone(node->name()))
				break;
			path = node->name() + "/" + path;
			node = node->parent();
		}
		if (animation.translation.keyframeCount() > 0)
			result->m_positionCurve.emplace_back(Vector3Curve{ path, animation.translation });
		if (animation.rotation.keyframeCount() > 0)
			result->m_rotationCurves.emplace_back(QuaternionCurve{ path, animation.rotation });
//		if (animation.eulers.keyframeCount() > 0)
//			result->m_eulersCurves.emplace_back(Vector3Curve{ path, animation.eulers});
		if (animation.scale.keyframeCount() > 0)
			result->m_scaleCurves.emplace_back(Vector3Curve{ path, animation.scale });
	}
	return result;
}

void FishEditor::FBXImporter::ImportAnimations(fbxsdk::FbxScene* scene)
{
	FbxNode * root = scene->GetRootNode();
	int numAnimStacks = scene->GetSrcObjectCount<FbxAnimStack>();
	for (int i = 0; i < numAnimStacks; ++i)
	{
		FbxAnimStack* animStack = scene->GetSrcObject<FbxAnimStack>(i);

		m_model.m_clips.emplace_back();
		auto & clip = m_model.m_clips.back();
		clip.name = animStack->GetName();
		FbxTimeSpan timeSpan = animStack->GetLocalTimeSpan();
		clip.start = (float)timeSpan.GetStart().GetSecondDouble();
		clip.end = (float)timeSpan.GetStop().GetSecondDouble();
		clip.sampleRate = (uint32_t)FbxTime::GetFrameRate(scene->GetGlobalSettings().GetTimeMode());

		int layerCount = animStack->GetMemberCount<FbxAnimLayer>();
		if (layerCount == 1)
		{
			FbxAnimLayer* animLayer = animStack->GetMember<FbxAnimLayer>(0);
			ImportAnimations(animLayer, root, clip);
		}
		else
		{
			//abort();
			LogError(Format("multiple animation clip in model: %1%", this->m_assetPath.string()));
		}
	}

	for (auto clip : m_model.m_clips)
	{
		auto animationClip = ConvertAnimationClip(clip);
		m_model.m_animationClips.push_back(animationClip);
		animationClip->m_avatar = m_model.m_avatar;
	}
}

TAnimationCurve<Vector3> reduceKeyframes(TAnimationCurve<Vector3>& curve)
{
	uint32_t keyCount = (uint32_t)curve.keyframeCount();

	std::vector<TKeyframe<Vector3>> newKeyframes;

	bool lastWasEqual = false;
	for (uint32_t i = 0; i < keyCount; i++)
	{
		bool isEqual = true;

		const TKeyframe<Vector3>& curKey = curve.keyframeAt(i);
		if (i > 0)
		{
			TKeyframe<Vector3>& prevKey = newKeyframes.back();

			isEqual = isEqual && (prevKey.value == curKey.value) && (prevKey.outTangent == curKey.inTangent);
		}
		else
			isEqual = false;

		// More than two keys in a row are equal, remove previous key by replacing it with this one
		if (lastWasEqual && isEqual)
		{
			TKeyframe<Vector3>& prevKey = newKeyframes.back();

			// Other properties are guaranteed unchanged
			prevKey.time = curKey.time;
			prevKey.outTangent = curKey.outTangent;

			continue;
		}

		newKeyframes.push_back(curKey);
		lastWasEqual = isEqual;
	}

	return TAnimationCurve<Vector3>(newKeyframes);
}

void SetKeyframeValues(TKeyframe<Vector3>& keyFrame, int idx, float value, float inTangent, float outTangent)
{
	keyFrame.value[idx] = value;
	keyFrame.inTangent[idx] = inTangent;
	keyFrame.outTangent[idx] = outTangent;
}

template<class T, int C>
TAnimationCurve<T> ImportCurve(FbxAnimCurve*(&fbxCurve)[C], float(&defaultValues)[C], float start, float end)
{
	int keyCounts[C];
	for (int i = 0; i < C; i++)
	{
		if (fbxCurve[i] != nullptr)
			keyCounts[i] = fbxCurve[i]->KeyGetCount();
		else
			keyCounts[i] = 0;
	}

	// If curve key-counts don't match, we need to force resampling
	bool forceResample = false;
	if (!forceResample)
	{
		for (int i = 1; i < C; i++)
		{
			forceResample |= keyCounts[i - 1] != keyCounts[i];
			if (forceResample)
				break;
		}
	}

	// Read keys directly
	if (!forceResample)
	{
		bool foundMismatch = false;
		int keyCount = keyCounts[0];
		std::vector<TKeyframe<T>> keyframes;
		for (int i = 0; i < keyCount; i++)
		{
			FbxTime fbxTime = fbxCurve[0]->KeyGetTime(i);
			float time = (float)fbxTime.GetSecondDouble();

			// Ensure times from other curves match
			for (int j = 1; j < C; j++)
			{
				fbxTime = fbxCurve[j]->KeyGetTime(i);
				float otherTime = (float)fbxTime.GetSecondDouble();

				if (!Mathf::CompareApproximately(time, otherTime))
				{
					foundMismatch = true;
					break;
				}
			}

			if (foundMismatch)
				break;

			if (time < start || time > end)
				continue;

			keyframes.push_back(TKeyframe<T>());
			TKeyframe<T>& keyFrame = keyframes.back();

			keyFrame.time = time;

			for (int j = 0; j < C; j++)
			{
				SetKeyframeValues(keyFrame, j,
					fbxCurve[j]->KeyGetValue(i),
					fbxCurve[j]->KeyGetLeftDerivative(i),
					fbxCurve[j]->KeyGetRightDerivative(i));
			}
		}

		if (!foundMismatch)
			return TAnimationCurve<T>(keyframes);
		else
			forceResample = true;
	}

	//if (!importOptions.animResample && forceResample)
	//	LOGWRN("Animation has different keyframes for different curve components, forcing resampling.");

	// Resample keys
	float curveStart = std::numeric_limits<float>::infinity();
	float curveEnd = -std::numeric_limits<float>::infinity();

	for (uint32_t i = 0; i < C; i++)
	{
		if (fbxCurve[i] == nullptr)
		{
			curveStart = std::min(0.0f, curveStart);
			curveEnd = std::max(0.0f, curveEnd);

			continue;
		}

		int keyCount = keyCounts[i];
		for (int j = 0; j < keyCount; j++)
		{
			FbxTime fbxTime = fbxCurve[i]->KeyGetTime(j);
			float time = (float)fbxTime.GetSecondDouble();

			curveStart = std::min(time, curveStart);
			curveEnd = std::max(time, curveEnd);
		}
	}

	curveStart = Mathf::Clamp(curveStart, start, end);
	curveEnd = Mathf::Clamp(curveEnd, start, end);

	float curveLength = curveEnd - curveStart;
	constexpr float animSampleRate = 1.0f / 30.0f;
	int numSamples = Mathf::CeilToInt(curveLength / animSampleRate);

	// We don't use the exact provided sample rate but instead modify it slightly so it
	// completely covers the curve range including start/end points while maintaining
	// constant time step between keyframes.
	float dt = curveLength / (float)numSamples;

	int lastKeyframe[] = { 0, 0, 0 };
	int lastLeftTangent[] = { 0, 0, 0 };
	int lastRightTangent[] = { 0, 0, 0 };

	std::vector<TKeyframe<T>> keyframes(numSamples);
	for (int i = 0; i < numSamples; i++)
	{
		float sampleTime = std::min(curveStart + i * dt, curveEnd);
		FbxTime fbxSampleTime;
		fbxSampleTime.SetSecondDouble(sampleTime);

		TKeyframe<T>& keyFrame = keyframes[i];
		keyFrame.time = sampleTime;

		for (int j = 0; j < C; j++)
		{
			if (fbxCurve[j] != nullptr)
			{
				SetKeyframeValues(keyFrame, j,
					fbxCurve[j]->Evaluate(fbxSampleTime, &lastKeyframe[j]),
					fbxCurve[j]->EvaluateLeftDerivative(fbxSampleTime, &lastLeftTangent[j]),
					fbxCurve[j]->EvaluateRightDerivative(fbxSampleTime, &lastRightTangent[j]));
			}
			else
			{
				SetKeyframeValues(keyFrame, j, defaultValues[C], 0.0f, 0.0f);
			}
		}
	}

	return TAnimationCurve<T>(keyframes);
}

void FishEditor::FBXImporter::ImportAnimations(fbxsdk::FbxAnimLayer* layer, fbxsdk::FbxNode * node, FBXAnimationClip & clip)
{
	FbxAnimCurve* translation[3];
	translation[0] = node->LclTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X);
	translation[1] = node->LclTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y);
	translation[2] = node->LclTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z);

	FbxAnimCurve* rotation[3];
	rotation[0] = node->LclRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X);
	rotation[1] = node->LclRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y);
	rotation[2] = node->LclRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z);

	FbxAnimCurve* scale[3];
	scale[0] = node->LclScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X);
	scale[1] = node->LclScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y);
	scale[2] = node->LclScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z);

	Vector3 defaultTranslation = FBXToNativeType(node->LclTranslation.Get());
	Vector3 defaultRotation = FBXToNativeType(node->LclRotation.Get());
	Vector3 defaultScale = FBXToNativeType(node->LclScaling.Get());

	auto hasCurveValues = [](FbxAnimCurve* curves[3])
	{
		for (int i = 0; i < 3; i++)
		{
			if (curves[i] != nullptr && curves[i]->KeyGetCount() > 0)
				return true;
		}

		return false;
	};

	EFbxRotationOrder order;
	node->GetRotationOrder(FbxNode::eDestinationPivot, order);
	RotationOrder rotationOrder = FBXToNativeType(order);

	bool hasBoneAnimation = hasCurveValues(translation) || hasCurveValues(rotation) || hasCurveValues(scale);

	if (order != eEulerXYZ && hasBoneAnimation)
	{
		LogWarning("here");
	}

	if (hasBoneAnimation)
	{
		clip.boneAnimations.push_back(FBXBoneAnimation());
		FBXBoneAnimation& boneAnim = clip.boneAnimations.back();
		boneAnim.node = m_model.m_fbxNodeLookup[node];

		if (hasCurveValues(translation))
		{
			float defaultValues[3];
			memcpy(defaultValues, &defaultTranslation, sizeof(defaultValues));

			boneAnim.translation = ImportCurve<Vector3, 3>(translation, defaultValues,
														   clip.start, clip.end);
		}
		//else
		//{
		//	std::vector<TKeyframe<Vector3>> keyframes(1);
		//	keyframes[0].value = defaultTranslation;
		//	keyframes[0].inTangent = Vector3::zero;
		//	keyframes[0].outTangent = Vector3::zero;
		//	boneAnim.translation = TAnimationCurve<Vector3>(keyframes);
		//}

		if (hasCurveValues(scale))
		{
			float defaultValues[3];
			memcpy(defaultValues, &defaultScale, sizeof(defaultValues));

			boneAnim.scale = ImportCurve<Vector3, 3>(scale, defaultValues, clip.start, clip.end);
		}
		//else
		//{
		//	std::vector<TKeyframe<Vector3>> keyframes(1);
		//	keyframes[0].value = defaultScale;
		//	keyframes[0].inTangent = Vector3::zero;
		//	keyframes[0].outTangent = Vector3::zero;
		//	boneAnim.scale = TAnimationCurve<Vector3>(keyframes);
		//}

		TAnimationCurve<Vector3> eulerAnimation;
		if (hasCurveValues(rotation))
		{
			float defaultValues[3];
			memcpy(defaultValues, &defaultRotation, sizeof(defaultValues));

			eulerAnimation = ImportCurve<Vector3, 3>(rotation, defaultValues, clip.start, clip.end);
		}
		//else
		//{
		//	std::vector<TKeyframe<Vector3>> keyframes(1);
		//	keyframes[0].value = defaultRotation;
		//	keyframes[0].inTangent = Vector3::zero;
		//	keyframes[0].outTangent = Vector3::zero;
		//	eulerAnimation = TAnimationCurve<Vector3>(keyframes);
		//}

		//if(importOptions.reduceKeyframes)
		//{
		//	boneAnim.translation = reduceKeyframes(boneAnim.translation);
		//	boneAnim.scale = reduceKeyframes(boneAnim.scale);
		//	eulerAnimation = reduceKeyframes(eulerAnimation);
		//}

		boneAnim.translation = AnimationCurveUtility::ScaleCurve(boneAnim.translation, m_fileScale * m_globalScale);
		//boneAnim.eulers = eulerAnimation;
		boneAnim.rotation = AnimationCurveUtility::EulerToQuaternionCurve(eulerAnimation, rotationOrder);

	}

	int childCount = node->GetChildCount();
	for (int i = 0; i < childCount; i++)
	{
		FbxNode* child = node->GetChild(i);
		ImportAnimations(layer, child, clip);
	}
}

max_stars_repo_name: ValtoGameEngines/Fish-Engine

max_issues_repo_name: ValtoGameEngines/Fish-Engine

max_forks_repo_name: yushroom/FishEngine


text:
#define BOOST_TEST_MODULE memory
#include <boost/test/unit_test.hpp>

#include <bunsan/memory.hpp>

#include <boost/smart_ptr/intrusive_ref_counter.hpp>

BOOST_AUTO_TEST_SUITE(memory)

BOOST_AUTO_TEST_CASE(std_unique_ptr) {
  std::unique_ptr<int> a = bunsan::make_ptr<std::unique_ptr<int>>(1);
  BOOST_REQUIRE(a);
  BOOST_CHECK_EQUAL(*a, 1);
}

BOOST_AUTO_TEST_CASE(std_shared_ptr) {
  std::shared_ptr<int> a = bunsan::make_ptr<std::shared_ptr<int>>(2);
  BOOST_REQUIRE(a);
  BOOST_CHECK_EQUAL(*a, 2);
}

struct counted : boost::intrusive_ref_counter<counted> {
  explicit counted(const int data_) : data(data_) {}
  int data;
};

BOOST_AUTO_TEST_CASE(boost_intrusive_ptr) {
  boost::intrusive_ptr<counted> a =
      bunsan::make_ptr<boost::intrusive_ptr<counted>>(3);
  BOOST_REQUIRE(a);
  BOOST_CHECK_EQUAL(a->data, 3);
}

BOOST_AUTO_TEST_CASE(boost_shared_ptr) {
  boost::shared_ptr<int> a = bunsan::make_ptr<boost::shared_ptr<int>>(4);
  BOOST_REQUIRE(a);
  BOOST_CHECK_EQUAL(*a, 4);
}

BOOST_AUTO_TEST_SUITE_END()  // memory


text:
#include "CorePch.h"
#include <rtp++/GroupedRtpSessionManager.h>
#include <boost/optional.hpp>
#include <cpputil/ExceptionBase.h>
#include <rtp++/application/ApplicationParameters.h>
#include <rtp++/application/ApplicationUtil.h>

using boost::optional;

namespace rtp_plus_plus
{

GroupedRtpSessionManager::GroupedRtpSessionManager(const GenericParameters& applicationParameters)
  :IRtpSessionManager(applicationParameters),
    m_eState(SS_STOPPED),
    m_uiByeReceivedCount(0),
    m_bExitOnBye(false),
    m_bAnalyse(false)
{
  optional<bool> exitOnBye = applicationParameters.getBoolParameter(app::ApplicationParameters::exit_on_bye);
  if (exitOnBye) m_bExitOnBye = *exitOnBye;

  optional<bool> bAnalyse = applicationParameters.getBoolParameter(app::ApplicationParameters::analyse);
  if (bAnalyse) m_bAnalyse = *bAnalyse;
}

GroupedRtpSessionManager::~GroupedRtpSessionManager()
{
  if (m_bAnalyse)
  {
    for (auto& mapEntry : m_mLostSequenceNumbers)
    {
      std::ostringstream ostr;
      std::vector<uint32_t>& vSequenceNumbers = mapEntry.second;
      if (!vSequenceNumbers.empty())
      {
        uint32_t uiMin = vSequenceNumbers[0];
        uint32_t uiMax = vSequenceNumbers[vSequenceNumbers.size() - 1];
        for (size_t i = uiMin; i <= uiMax; ++i)
        {
          auto it = std::find_if(vSequenceNumbers.begin(), vSequenceNumbers.end(), [i](uint32_t uiSN)
          {
            return (uint32_t)i == uiSN;
          });
          if (it == vSequenceNumbers.end())
          {
            // couldn't find it
            ostr << i << " ";
          }
        }
      }
    VLOG(2) << "RTP sequence number losses. Mid: " << mapEntry.first << " : "
              << ostr.str();
    }
  }
}

void GroupedRtpSessionManager::addRtpSession(const RtpSessionParameters& rtpParameters,
                                             RtpSession::ptr pRtpSession)
{
  m_vRtpParameters.push_back(rtpParameters);
  m_mRtpSessions[rtpParameters.getMid()] = pRtpSession;
  m_vMids.push_back(rtpParameters.getMid());

  pRtpSession->setIncomingRtpHandler(
    boost::bind(&GroupedRtpSessionManager::handleIncomingRtp, this, _1, _2, _3, _4, _5, rtpParameters.getMid()));

  pRtpSession->setIncomingRtcpHandler(
    boost::bind(&GroupedRtpSessionManager::handleIncomingRtcp, this, _1, _2, rtpParameters.getMid()));

  pRtpSession->setMemberUpdateHandler(
    boost::bind(&GroupedRtpSessionManager::handleGroupedMemberUpdate, this, _1, rtpParameters.getMid()));

  onRtpSessionAdded(rtpParameters.getMid(), rtpParameters, pRtpSession);
}

std::vector<std::string> GroupedRtpSessionManager::getMediaTypes() const
{
  std::vector<std::string> vMediaTypes;
  for (const RtpSessionParameters& rtpParameter : m_vRtpParameters )
  {
    vMediaTypes.push_back(rtpParameter.getEncodingName());
  }
  return vMediaTypes;
}

std::string GroupedRtpSessionManager::getPrimaryMediaType() const
{
  // TODO: will this always be the first one???
  return m_vRtpParameters[0].getEncodingName();
}

const RtpSessionParameters& GroupedRtpSessionManager::getPrimaryRtpSessionParameters() const
{
  if (m_vRtpParameters.empty())
    BOOST_THROW_EXCEPTION(ExceptionBase("No RTP sessions have been set"));

  return m_vRtpParameters[0];
}

RtpSession& GroupedRtpSessionManager::getPrimaryRtpSession()
{
  if (m_vMids.empty())
    BOOST_THROW_EXCEPTION(ExceptionBase("No RTP sessions have been set"));
  return *(m_mRtpSessions[m_vMids[0]].get());
}

boost::system::error_code GroupedRtpSessionManager::start()
{
  if (m_eState == SS_STOPPED)
  {
    VLOG(2) << "[" << this << "] Starting media session";
    for (auto& pair :  m_mRtpSessions)
    {
      VLOG(2) << "[" << this << "] Starting RTP session with MID: " << pair.first;
      boost::system::error_code ec = pair.second->start();
      if (ec)
      {
        LOG(WARNING) << "Failed to start RTP session: " << ec.message();
        return ec;
      }
    }
    onSessionsStarted();

    m_eState = SS_STARTED;
    return boost::system::error_code();
  }
  else
  {
    LOG(WARNING) << "Session manager already started: " << m_eState;
    return boost::system::error_code(boost::system::errc::invalid_argument, boost::system::get_generic_category());
  }
}

boost::system::error_code GroupedRtpSessionManager::stop()
{
  VLOG(2) << "[" << this << "] Stopping media session";
  if (m_eState == SS_STARTED)
  {
    boost::system::error_code res_ec;
      // stop all RTP sessions
    for (auto& pair :  m_mRtpSessions)
    {
      VLOG(2) << "[" << this << "] Stopping RTP session with MID: " << pair.first;
      boost::system::error_code ec = pair.second->stop();
      if (ec)
      {
        LOG(WARNING) << "Failed to stop RTP session: " << ec.message();
        // NOTE: shortcoming of this code is that we can only return one error (the last one)
        res_ec = ec;
      }
    }
    onSessionsStopped();

    m_eState = SS_STOPPED;
    return res_ec;
  }
  else
  {
    LOG(WARNING) << "Session manager already stopped: " << m_eState;
    return boost::system::error_code(boost::system::errc::invalid_argument, boost::system::get_generic_category());
  }
}

void GroupedRtpSessionManager::send(const media::MediaSample& mediaSample)
{
  return doSend(mediaSample);
}

void GroupedRtpSessionManager::send(const std::vector<media::MediaSample>& vMediaSamples)
{
  return doSend(vMediaSamples);
}

void GroupedRtpSessionManager::send(const media::MediaSample& mediaSample, const std::string& sMid)
{
  return doSend(mediaSample, sMid);
}

void GroupedRtpSessionManager::send(const std::vector<media::MediaSample>& vMediaSamples, const std::string& sMid)
{
  return doSend(vMediaSamples, sMid);
}

bool GroupedRtpSessionManager::isSampleAvailable() const
{
  return doIsSampleAvailable();
}

bool GroupedRtpSessionManager::isSampleAvailable(const std::string& sMid) const
{
  return doIsSampleAvailable(sMid);
}

std::vector<media::MediaSample> GroupedRtpSessionManager::getNextSample()
{
  return doGetNextSample();
}

std::vector<media::MediaSample> GroupedRtpSessionManager::getNextSample(const std::string& sMid)
{
  return doGetNextSample(sMid);
}

RtpSession::ptr GroupedRtpSessionManager::getRtpSession(const std::string& sMid)
{
  auto it = m_mRtpSessions.find(sMid);
  if (it == m_mRtpSessions.end())
    return RtpSession::ptr();
  else
    return m_mRtpSessions[sMid];
}

const std::string& GroupedRtpSessionManager::getMid(uint32_t uiIndex) const
{
  return m_vMids.at(uiIndex);
}

const uint32_t GroupedRtpSessionManager::getSessionCount() const
{
  return m_vMids.size();
}

void GroupedRtpSessionManager::onRtpSessionAdded(const std::string& sMid,
                                                 const RtpSessionParameters& rtpParameters,
                                                 RtpSession::ptr pRtpSession)
{
  // NOOP
}

void GroupedRtpSessionManager::handleIncomingRtp(const RtpPacket &rtpPacket, const EndPoint &ep,
                                                 bool bSSRCValidated, bool bRtcpSynchronised,
                                                 const boost::posix_time::ptime &tPresentation,
                                                 const std::string &sMid)
{
  VLOG(15) << "RTP [IN] SN: " << rtpPacket.getExtendedSequenceNumber()
           << " PTS: " << tPresentation
           << " From: " << ep
           << " Valid: " << bSSRCValidated
           << " Sync: " << bRtcpSynchronised
           << " Size: " << rtpPacket.getSize()
           << " RTP TS: " << rtpPacket.getRtpTimestamp()
           << " Mid: " << sMid;

  doHandleIncomingRtp(rtpPacket, ep, bSSRCValidated, bRtcpSynchronised, tPresentation, sMid);

  if (m_bAnalyse)
  {
    m_mLostSequenceNumbers[sMid].push_back(rtpPacket.getExtendedSequenceNumber());
  }
}

void GroupedRtpSessionManager::handleIncomingRtcp(const CompoundRtcpPacket &compoundRtcp,
                                                  const EndPoint &ep, const std::string &sMid)
{
  VLOG(15) << "Incoming RTCP from : " << ep << " Mid: " << sMid;
  doHandleIncomingRtcp(compoundRtcp, ep, sMid);
}

void GroupedRtpSessionManager::handleGroupedMemberUpdate(const MemberUpdate& memberUpdate, const std::string &sMid)
{
  VLOG(15) << "Member update from Mid: " << sMid << ":" << memberUpdate;
  if (memberUpdate.isMemberJoin())
  {

  }
  else if (memberUpdate.isMemberUpdate())
  {

  }
  else if (memberUpdate.isMemberLeave())
  {
    VLOG(2) << "Bye received.";
    if( m_bExitOnBye)
    {
      ++m_uiByeReceivedCount;

#ifdef WAIT_FOR_ALL_BYES
      // only once all byes have been received
      // if BYE is lost the application won't end
      if (m_uiByeReceivedCount == m_vMids.size())
#else
      // only on first BYE
      if (m_uiByeReceivedCount == 1)
#endif
      {
        VLOG(2) << "Shutting down session.";
        stop();
        VLOG(2) << "Session stopped.";
        // HACK for now
        app::ApplicationUtil::handleApplicationExit(boost::system::error_code(), boost::shared_ptr<SimpleMediaSession>());
        VLOG(2) << "Session stop signal fired.";
      }
    }
  }
}

} // rtp_plus_plus


text:
/*

Copyright (c) 2020, Paul-Louis Ageneau
Copyright (c) 2018, Alexandre Janniaux
Copyright (c) 2020, Steven Siloti
Copyright (c) 2020, Arvid Norberg
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the distribution.
  * Neither the name of the author nor the names of its
    contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef TORRENT_SSL_HPP_INCLUDED
#define TORRENT_SSL_HPP_INCLUDED

#include "libtorrent/config.hpp"
#include "libtorrent/string_view.hpp"
#include "libtorrent/aux_/export.hpp"

#if TORRENT_USE_SSL

#include "libtorrent/aux_/disable_warnings_push.hpp"

#include <boost/asio.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp>

#ifndef BOOST_NO_EXCEPTIONS
#include <boost/system/system_error.hpp>
#endif

#ifdef TORRENT_USE_OPENSSL
#include <openssl/opensslv.h> // for OPENSSL_VERSION_NUMBER
#if OPENSSL_VERSION_NUMBER < 0x1000000fL
#error OpenSSL too old, use a recent version with SNI support
#endif
#ifdef TORRENT_WINDOWS
// because openssl includes winsock.h, we must include winsock2.h first
#include <winsock2.h>
#endif
#include <openssl/ssl.h>
#include <openssl/crypto.h>
#include <openssl/rand.h>
#include <boost/asio/ssl.hpp>
#endif

#ifdef TORRENT_USE_GNUTLS
#include <gnutls/gnutls.h>
#include <boost/asio/gnutls.hpp>
#endif

#include "libtorrent/aux_/disable_warnings_pop.hpp"

#ifdef TORRENT_BUILD_SIMULATOR
#include "simulator/simulator.hpp"
#endif

#include <string>
#include <cstddef>
#include <functional>
#include <exception>

namespace libtorrent {
namespace ssl {

using error_code = boost::system::error_code;

#if defined TORRENT_USE_OPENSSL
#if defined TORRENT_BUILD_SIMULATOR
	using sim::asio::ssl::context;
	using sim::asio::ssl::stream_base;
	using sim::asio::ssl::stream;
#else
	using boost::asio::ssl::context;
	using boost::asio::ssl::stream_base;
	using boost::asio::ssl::stream;
#endif
using boost::asio::ssl::verify_context;
#if BOOST_VERSION >= 107300
using boost::asio::ssl::host_name_verification;
#else
using host_name_verification = boost::asio::ssl::rfc2818_verification;
#endif

using native_context_type = SSL_CTX*;
using native_stream_type = SSL*;
using context_handle_type = native_context_type;
using stream_handle_type = native_stream_type;

typedef int (*server_name_callback_type)(SSL* s, int*, void* arg);

#elif defined TORRENT_USE_GNUTLS
using boost::asio::gnutls::context;
using boost::asio::gnutls::stream_base;
using boost::asio::gnutls::stream;
using boost::asio::gnutls::verify_context;
using boost::asio::gnutls::host_name_verification;

using native_context_type = context::native_handle_type;
using native_stream_type = stream_base::native_handle_type;
using context_handle_type = context*;
using stream_handle_type = stream_base*;

typedef bool (*server_name_callback_type)(stream_handle_type handle, std::string const& name, void* arg);

#endif

namespace error {

#if defined TORRENT_USE_OPENSSL
using boost::asio::error::get_ssl_category;
using boost::asio::ssl::error::get_stream_category;
#elif defined TORRENT_USE_GNUTLS
using boost::asio::gnutls::error::get_ssl_category;
using boost::asio::gnutls::error::get_stream_category;
#endif

}

inline context_handle_type get_handle(context &c)
{
#if defined TORRENT_USE_OPENSSL
	return c.native_handle();
#elif defined TORRENT_USE_GNUTLS
	return &c;
#endif
}

template<typename T>
stream_handle_type get_handle(stream<T>& s)
{
#if defined TORRENT_USE_OPENSSL
	return s.native_handle();
#elif defined TORRENT_USE_GNUTLS
	return &s;
#endif
}

template<typename T>
context_handle_type get_context_handle(stream<T>& s)
{
#if defined TORRENT_USE_OPENSSL
	return SSL_get_SSL_CTX(s.native_handle());
#elif defined TORRENT_USE_GNUTLS
	return &s.get_context();
#endif
}

TORRENT_EXTRA_EXPORT void set_trust_certificate(native_context_type nc, string_view pem, error_code &ec);

TORRENT_EXTRA_EXPORT void set_server_name_callback(context_handle_type c, server_name_callback_type cb, void* arg, error_code& ec);
TORRENT_EXTRA_EXPORT void set_host_name(stream_handle_type s, std::string const& name, error_code& ec);

TORRENT_EXTRA_EXPORT void set_context(stream_handle_type s, context_handle_type c);
TORRENT_EXTRA_EXPORT bool has_context(stream_handle_type s, context_handle_type c);
TORRENT_EXTRA_EXPORT context_handle_type get_context(stream_handle_type s);

} // ssl
} // libtorrent

#endif // TORRENT_USE_SSL

#endif

ext: hpp

max_stars_repo_name: stdjs/libtorrent

max_issues_repo_name: stdjs/libtorrent

max_forks_repo_name: stdjs/libtorrent