<commit_before>/*
 * xtensor-fftw
 * Copyright (c) 2017, Patrick Bos
 * Distributed under the terms of the BSD 3-Clause License.
 *
 * The full license is in the file LICENSE, distributed with this software.
 */

#include <xtensor/xarray.hpp>
#include <xtensor-fftw/helper.hpp>

#include "gtest/gtest.h"

TEST(helper, fftshift) {
  xt::xarray<double> odd = {3, 4, 0, 1, 2};
  xt::xarray<double> even = {3, 4, 5, 0, 1, 2};
  xt::xarray<double> odd_range = xt::arange<double>(5);
  xt::xarray<double> even_range = xt::arange<double>(6);
  EXPECT_EQ(xt::fftw::fftshift(odd_range), odd);
  EXPECT_EQ(xt::fftw::fftshift(even_range), even);
}

TEST(helper, ifftshift) {
  xt::xarray<double> odd = {3, 4, 0, 1, 2};
  xt::xarray<double> even = {3, 4, 5, 0, 1, 2};
  EXPECT_EQ(xt::fftw::ifftshift(odd), xt::arange<double>(5));
  EXPECT_EQ(xt::fftw::ifftshift(even), xt::arange<double>(6));
}

TEST(helper, fftfreq) {
  xt::xarray<double> reference9 = {0.,  1.,  2.,  3.,  4., -4., -3., -2., -1.};
  xt::xarray<double> reference10 = {0.  ,  0.04,  0.08,  0.12,  0.16, -0.2 , -0.16, -0.12, -0.08, -0.04};
  EXPECT_EQ(xt::fftw::fftfreq(9, 1./9), reference9);
  EXPECT_EQ(xt::fftw::fftfreq(10, 2.5), reference10);
}

TEST(helper, fftscale) {
  xt::xarray<double> reference9 = {0.,  1.,  2.,  3.,  4., -4., -3., -2., -1.};
  xt::xarray<double> reference10 = {0.  ,  0.04,  0.08,  0.12,  0.16, -0.2 , -0.16, -0.12, -0.08, -0.04};
  EXPECT_EQ(xt::fftw::fftscale(9, 1./9), 2 * M_PI * reference9);
  EXPECT_EQ(xt::fftw::fftscale(10, 2.5), 2 * M_PI * reference10);
}

TEST(helper, rfftfreq) {
  xt::xarray<double> reference9 = {0.,  1.,  2.,  3.,  4.};
  xt::xarray<double> reference10 = {0.  ,  0.04,  0.08,  0.12,  0.16, 0.2};
  EXPECT_EQ(xt::fftw::rfftfreq(9, 1./9), reference9);
  EXPECT_EQ(xt::fftw::rfftfreq(10, 2.5), reference10);
}

TEST(helper, rfftscale) {
  xt::xarray<double> reference9 = {0.,  1.,  2.,  3.,  4.};
  xt::xarray<double> reference10 = {0.  ,  0.04,  0.08,  0.12,  0.16, 0.2};
  EXPECT_EQ(xt::fftw::rfftscale(9, 1./9), 2 * M_PI * reference9);
  EXPECT_EQ(xt::fftw::rfftscale(10, 2.5), 2 * M_PI * reference10);
}
<commit_msg>Fix helper tests on Appveyor Windows<commit_after>/*
 * xtensor-fftw
 * Copyright (c) 2017, Patrick Bos
 * Distributed under the terms of the BSD 3-Clause License.
 *
 * The full license is in the file LICENSE, distributed with this software.
 */

#include <xtensor/xarray.hpp>
#include <xtensor-fftw/helper.hpp>
#include <cmath>  // M_PI

#include "gtest/gtest.h"

TEST(helper, fftshift) {
  xt::xarray<double> odd = {3, 4, 0, 1, 2};
  xt::xarray<double> even = {3, 4, 5, 0, 1, 2};
  xt::xarray<double> odd_range = xt::arange<double>(5);
  xt::xarray<double> even_range = xt::arange<double>(6);
  EXPECT_EQ(xt::fftw::fftshift(odd_range), odd);
  EXPECT_EQ(xt::fftw::fftshift(even_range), even);
}

TEST(helper, ifftshift) {
  xt::xarray<double> odd = {3, 4, 0, 1, 2};
  xt::xarray<double> even = {3, 4, 5, 0, 1, 2};
  EXPECT_EQ(xt::fftw::ifftshift(odd), xt::arange<double>(5));
  EXPECT_EQ(xt::fftw::ifftshift(even), xt::arange<double>(6));
}

TEST(helper, fftfreq) {
  xt::xarray<double> reference9 = {0.,  1.,  2.,  3.,  4., -4., -3., -2., -1.};
  xt::xarray<double> reference10 = {0.  ,  0.04,  0.08,  0.12,  0.16, -0.2 , -0.16, -0.12, -0.08, -0.04};
  EXPECT_EQ(xt::fftw::fftfreq(9, 1./9), reference9);
  EXPECT_EQ(xt::fftw::fftfreq(10, 2.5), reference10);
}

TEST(helper, fftscale) {
  xt::xarray<double> reference9 = {0.,  1.,  2.,  3.,  4., -4., -3., -2., -1.};
  xt::xarray<double> reference10 = {0.  ,  0.04,  0.08,  0.12,  0.16, -0.2 , -0.16, -0.12, -0.08, -0.04};
  EXPECT_EQ(xt::fftw::fftscale(9, 1./9), 2 * M_PI * reference9);
  EXPECT_EQ(xt::fftw::fftscale(10, 2.5), 2 * M_PI * reference10);
}

TEST(helper, rfftfreq) {
  xt::xarray<double> reference9 = {0.,  1.,  2.,  3.,  4.};
  xt::xarray<double> reference10 = {0.  ,  0.04,  0.08,  0.12,  0.16, 0.2};
  EXPECT_EQ(xt::fftw::rfftfreq(9, 1./9), reference9);
  EXPECT_EQ(xt::fftw::rfftfreq(10, 2.5), reference10);
}

TEST(helper, rfftscale) {
  xt::xarray<double> reference9 = {0.,  1.,  2.,  3.,  4.};
  xt::xarray<double> reference10 = {0.  ,  0.04,  0.08,  0.12,  0.16, 0.2};
  EXPECT_EQ(xt::fftw::rfftscale(9, 1./9), 2 * M_PI * reference9);
  EXPECT_EQ(xt::fftw::rfftscale(10, 2.5), 2 * M_PI * reference10);
}
<|endoftext|>


<commit_before>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/socket/tcp_client_socket_libevent.h"

#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>

#include "base/eintr_wrapper.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/trace_event.h"
#include "net/base/io_buffer.h"
#include "net/base/load_log.h"
#include "net/base/net_errors.h"
#include "third_party/libevent/event.h"

namespace net {

namespace {

const int kInvalidSocket = -1;

// Return 0 on success, -1 on failure.
// Too small a function to bother putting in a library?
int SetNonBlocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (-1 == flags)
      return flags;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

// Convert values from <errno.h> to values from "net/base/net_errors.h"
int MapPosixError(int os_error) {
  // There are numerous posix error codes, but these are the ones we thus far
  // find interesting.
  switch (os_error) {
    case EAGAIN:
#if EWOULDBLOCK != EAGAIN
    case EWOULDBLOCK:
#endif
      return ERR_IO_PENDING;
    case EACCES:
      return ERR_ACCESS_DENIED;
    case ENETDOWN:
      return ERR_INTERNET_DISCONNECTED;
    case ETIMEDOUT:
      return ERR_TIMED_OUT;
    case ECONNRESET:
    case ENETRESET:  // Related to keep-alive
      return ERR_CONNECTION_RESET;
    case ECONNABORTED:
      return ERR_CONNECTION_ABORTED;
    case ECONNREFUSED:
      return ERR_CONNECTION_REFUSED;
    case EHOSTUNREACH:
    case ENETUNREACH:
      return ERR_ADDRESS_UNREACHABLE;
    case EADDRNOTAVAIL:
      return ERR_ADDRESS_INVALID;
    case 0:
      return OK;
    default:
      LOG(WARNING) << "Unknown error " << os_error
                   << " mapped to net::ERR_FAILED";
      return ERR_FAILED;
  }
}

int MapConnectError(int os_error) {
  switch (os_error) {
    case ETIMEDOUT:
      return ERR_CONNECTION_TIMED_OUT;
    default: {
      int net_error = MapPosixError(os_error);
      if (net_error == ERR_FAILED)
        return ERR_CONNECTION_FAILED;  // More specific than ERR_FAILED.
      return net_error;
    }
  }
}

// Given os_error, an errno from a connect() attempt, returns true if
// connect() should be retried with another address.
bool ShouldTryNextAddress(int os_error) {
  switch (os_error) {
    case EADDRNOTAVAIL:
    case EAFNOSUPPORT:
    case ECONNREFUSED:
    case ECONNRESET:
    case EACCES:
    case EPERM:
    case ENETUNREACH:
    case EHOSTUNREACH:
    case ENETDOWN:
    case ETIMEDOUT:
      return true;
    default:
      return false;
  }
}

}  // namespace

//-----------------------------------------------------------------------------

TCPClientSocketLibevent::TCPClientSocketLibevent(const AddressList& addresses)
    : socket_(kInvalidSocket),
      addresses_(addresses),
      current_ai_(addresses_.head()),
      waiting_connect_(false),
      read_watcher_(this),
      write_watcher_(this),
      read_callback_(NULL),
      write_callback_(NULL) {
}

TCPClientSocketLibevent::~TCPClientSocketLibevent() {
  Disconnect();
}

int TCPClientSocketLibevent::Connect(CompletionCallback* callback,
                                     LoadLog* load_log) {
  // If already connected, then just return OK.
  if (socket_ != kInvalidSocket)
    return OK;

  DCHECK(!waiting_connect_);
  DCHECK(!load_log_);

  TRACE_EVENT_BEGIN("socket.connect", this, "");

  LoadLog::BeginEvent(load_log, LoadLog::TYPE_TCP_CONNECT);

  int rv = DoConnect();

  if (rv == ERR_IO_PENDING) {
    // Synchronous operation not supported.
    DCHECK(callback);

    load_log_ = load_log;
    waiting_connect_ = true;
    write_callback_ = callback;
  } else {
    TRACE_EVENT_END("socket.connect", this, "");
    LoadLog::EndEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
  }

  return rv;
}

int TCPClientSocketLibevent::DoConnect() {
  while (true) {
    DCHECK(current_ai_);

    int rv = CreateSocket(current_ai_);
    if (rv != OK)
      return rv;

    if (!HANDLE_EINTR(connect(socket_, current_ai_->ai_addr,
                              static_cast<int>(current_ai_->ai_addrlen)))) {
      // Connected without waiting!
      return OK;
    }

    int os_error = errno;
    if (os_error == EINPROGRESS)
      break;

    close(socket_);
    socket_ = kInvalidSocket;

    if (current_ai_->ai_next && ShouldTryNextAddress(os_error)) {
      // connect() can fail synchronously for an address even on a
      // non-blocking socket.  As an example, this can happen when there is
      // no route to the host.  Retry using the next address in the list.
      current_ai_ = current_ai_->ai_next;
    } else {
      DLOG(INFO) << "connect failed: " << os_error;
      return MapConnectError(os_error);
    }
  }

  // Initialize write_socket_watcher_ and link it to our MessagePump.
  // POLLOUT is set if the connection is established.
  // POLLIN is set if the connection fails.
  if (!MessageLoopForIO::current()->WatchFileDescriptor(
          socket_, true, MessageLoopForIO::WATCH_WRITE, &write_socket_watcher_,
          &write_watcher_)) {
    DLOG(INFO) << "WatchFileDescriptor failed: " << errno;
    close(socket_);
    socket_ = kInvalidSocket;
    return MapPosixError(errno);
  }

  return ERR_IO_PENDING;
}

void TCPClientSocketLibevent::Disconnect() {
  if (socket_ == kInvalidSocket)
    return;

  TRACE_EVENT_INSTANT("socket.disconnect", this, "");

  bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
  DCHECK(ok);
  ok = write_socket_watcher_.StopWatchingFileDescriptor();
  DCHECK(ok);
  close(socket_);
  socket_ = kInvalidSocket;
  waiting_connect_ = false;

  // Reset for next time.
  current_ai_ = addresses_.head();
}

bool TCPClientSocketLibevent::IsConnected() const {
  if (socket_ == kInvalidSocket || waiting_connect_)
    return false;

  // Check if connection is alive.
  char c;
  int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK));
  if (rv == 0)
    return false;
  if (rv == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
    return false;

  return true;
}

bool TCPClientSocketLibevent::IsConnectedAndIdle() const {
  if (socket_ == kInvalidSocket || waiting_connect_)
    return false;

  // Check if connection is alive and we haven't received any data
  // unexpectedly.
  char c;
  int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK));
  if (rv >= 0)
    return false;
  if (errno != EAGAIN && errno != EWOULDBLOCK)
    return false;

  return true;
}

int TCPClientSocketLibevent::Read(IOBuffer* buf,
                                  int buf_len,
                                  CompletionCallback* callback) {
  DCHECK_NE(kInvalidSocket, socket_);
  DCHECK(!waiting_connect_);
  DCHECK(!read_callback_);
  // Synchronous operation not supported
  DCHECK(callback);
  DCHECK_GT(buf_len, 0);

  TRACE_EVENT_BEGIN("socket.read", this, "");
  int nread = HANDLE_EINTR(read(socket_, buf->data(), buf_len));
  if (nread >= 0) {
    TRACE_EVENT_END("socket.read", this, StringPrintf("%d bytes", nread));
    return nread;
  }
  if (errno != EAGAIN && errno != EWOULDBLOCK) {
    DLOG(INFO) << "read failed, errno " << errno;
    return MapPosixError(errno);
  }

  if (!MessageLoopForIO::current()->WatchFileDescriptor(
          socket_, true, MessageLoopForIO::WATCH_READ,
          &read_socket_watcher_, &read_watcher_)) {
    DLOG(INFO) << "WatchFileDescriptor failed on read, errno " << errno;
    return MapPosixError(errno);
  }

  read_buf_ = buf;
  read_buf_len_ = buf_len;
  read_callback_ = callback;
  return ERR_IO_PENDING;
}

int TCPClientSocketLibevent::Write(IOBuffer* buf,
                                   int buf_len,
                                   CompletionCallback* callback) {
  DCHECK_NE(kInvalidSocket, socket_);
  DCHECK(!waiting_connect_);
  DCHECK(!write_callback_);
  // Synchronous operation not supported
  DCHECK(callback);
  DCHECK_GT(buf_len, 0);

  TRACE_EVENT_BEGIN("socket.write", this, "");
  int nwrite = HANDLE_EINTR(write(socket_, buf->data(), buf_len));
  if (nwrite >= 0) {
    TRACE_EVENT_END("socket.write", this, StringPrintf("%d bytes", nwrite));
    return nwrite;
  }
  if (errno != EAGAIN && errno != EWOULDBLOCK)
    return MapPosixError(errno);

  if (!MessageLoopForIO::current()->WatchFileDescriptor(
          socket_, true, MessageLoopForIO::WATCH_WRITE,
          &write_socket_watcher_, &write_watcher_)) {
    DLOG(INFO) << "WatchFileDescriptor failed on write, errno " << errno;
    return MapPosixError(errno);
  }

  write_buf_ = buf;
  write_buf_len_ = buf_len;
  write_callback_ = callback;
  return ERR_IO_PENDING;
}

bool TCPClientSocketLibevent::SetReceiveBufferSize(int32 size) {
  int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF,
      reinterpret_cast<const char*>(&size),
      sizeof(size));
  DCHECK(!rv) << "Could not set socket receive buffer size: " << errno;
  return rv == 0;
}

bool TCPClientSocketLibevent::SetSendBufferSize(int32 size) {
  int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
      reinterpret_cast<const char*>(&size),
      sizeof(size));
  DCHECK(!rv) << "Could not set socket send buffer size: " << errno;
  return rv == 0;
}

int TCPClientSocketLibevent::CreateSocket(const addrinfo* ai) {
  socket_ = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
  if (socket_ == kInvalidSocket)
    return MapPosixError(errno);

  if (SetNonBlocking(socket_))
    return MapPosixError(errno);

  return OK;
}

void TCPClientSocketLibevent::DoReadCallback(int rv) {
  DCHECK_NE(rv, ERR_IO_PENDING);
  DCHECK(read_callback_);

  // since Run may result in Read being called, clear read_callback_ up front.
  CompletionCallback* c = read_callback_;
  read_callback_ = NULL;
  c->Run(rv);
}

void TCPClientSocketLibevent::DoWriteCallback(int rv) {
  DCHECK_NE(rv, ERR_IO_PENDING);
  DCHECK(write_callback_);

  // since Run may result in Write being called, clear write_callback_ up front.
  CompletionCallback* c = write_callback_;
  write_callback_ = NULL;
  c->Run(rv);
}

void TCPClientSocketLibevent::DidCompleteConnect() {
  int result = ERR_UNEXPECTED;

  // Check to see if connect succeeded
  int os_error = 0;
  socklen_t len = sizeof(os_error);
  if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &os_error, &len) < 0)
    os_error = errno;

  if (os_error == EINPROGRESS || os_error == EALREADY) {
    NOTREACHED();  // This indicates a bug in libevent or our code.
    result = ERR_IO_PENDING;
  } else if (current_ai_->ai_next && ShouldTryNextAddress(os_error)) {
    // This address failed, try next one in list.
    const addrinfo* next = current_ai_->ai_next;
    Disconnect();
    current_ai_ = next;
    scoped_refptr<LoadLog> load_log;
    load_log.swap(load_log_);
    TRACE_EVENT_END("socket.connect", this, "");
    LoadLog::EndEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
    result = Connect(write_callback_, load_log);
  } else {
    result = MapConnectError(os_error);
    bool ok = write_socket_watcher_.StopWatchingFileDescriptor();
    DCHECK(ok);
    waiting_connect_ = false;
    TRACE_EVENT_END("socket.connect", this, "");
    LoadLog::EndEvent(load_log_, LoadLog::TYPE_TCP_CONNECT);
    load_log_ = NULL;
  }

  if (result != ERR_IO_PENDING) {
    DoWriteCallback(result);
  }
}

void TCPClientSocketLibevent::DidCompleteRead() {
  int bytes_transferred;
  bytes_transferred = HANDLE_EINTR(read(socket_, read_buf_->data(),
                                        read_buf_len_));

  int result;
  if (bytes_transferred >= 0) {
    TRACE_EVENT_END("socket.read", this,
                    StringPrintf("%d bytes", bytes_transferred));
    result = bytes_transferred;
  } else {
    result = MapPosixError(errno);
  }

  if (result != ERR_IO_PENDING) {
    read_buf_ = NULL;
    read_buf_len_ = 0;
    bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
    DCHECK(ok);
    DoReadCallback(result);
  }
}

void TCPClientSocketLibevent::DidCompleteWrite() {
  int bytes_transferred;
  bytes_transferred = HANDLE_EINTR(write(socket_, write_buf_->data(),
                                         write_buf_len_));

  int result;
  if (bytes_transferred >= 0) {
    result = bytes_transferred;
    TRACE_EVENT_END("socket.write", this,
                    StringPrintf("%d bytes", bytes_transferred));
  } else {
    result = MapPosixError(errno);
  }

  if (result != ERR_IO_PENDING) {
    write_buf_ = NULL;
    write_buf_len_ = 0;
    write_socket_watcher_.StopWatchingFileDescriptor();
    DoWriteCallback(result);
  }
}

int TCPClientSocketLibevent::GetPeerName(struct sockaddr* name,
                                         socklen_t* namelen) {
  return ::getpeername(socket_, name, namelen);
}

}  // namespace net
<commit_msg>Map EPIPE to ERR_CONNECTION_RESET.<commit_after>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/socket/tcp_client_socket_libevent.h"

#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>

#include "base/eintr_wrapper.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/trace_event.h"
#include "net/base/io_buffer.h"
#include "net/base/load_log.h"
#include "net/base/net_errors.h"
#include "third_party/libevent/event.h"

namespace net {

namespace {

const int kInvalidSocket = -1;

// Return 0 on success, -1 on failure.
// Too small a function to bother putting in a library?
int SetNonBlocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (-1 == flags)
      return flags;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

// Convert values from <errno.h> to values from "net/base/net_errors.h"
int MapPosixError(int os_error) {
  // There are numerous posix error codes, but these are the ones we thus far
  // find interesting.
  switch (os_error) {
    case EAGAIN:
#if EWOULDBLOCK != EAGAIN
    case EWOULDBLOCK:
#endif
      return ERR_IO_PENDING;
    case EACCES:
      return ERR_ACCESS_DENIED;
    case ENETDOWN:
      return ERR_INTERNET_DISCONNECTED;
    case ETIMEDOUT:
      return ERR_TIMED_OUT;
    case ECONNRESET:
    case ENETRESET:  // Related to keep-alive
    case EPIPE:
      return ERR_CONNECTION_RESET;
    case ECONNABORTED:
      return ERR_CONNECTION_ABORTED;
    case ECONNREFUSED:
      return ERR_CONNECTION_REFUSED;
    case EHOSTUNREACH:
    case ENETUNREACH:
      return ERR_ADDRESS_UNREACHABLE;
    case EADDRNOTAVAIL:
      return ERR_ADDRESS_INVALID;
    case 0:
      return OK;
    default:
      LOG(WARNING) << "Unknown error " << os_error
                   << " mapped to net::ERR_FAILED";
      return ERR_FAILED;
  }
}

int MapConnectError(int os_error) {
  switch (os_error) {
    case ETIMEDOUT:
      return ERR_CONNECTION_TIMED_OUT;
    default: {
      int net_error = MapPosixError(os_error);
      if (net_error == ERR_FAILED)
        return ERR_CONNECTION_FAILED;  // More specific than ERR_FAILED.
      return net_error;
    }
  }
}

// Given os_error, an errno from a connect() attempt, returns true if
// connect() should be retried with another address.
bool ShouldTryNextAddress(int os_error) {
  switch (os_error) {
    case EADDRNOTAVAIL:
    case EAFNOSUPPORT:
    case ECONNREFUSED:
    case ECONNRESET:
    case EACCES:
    case EPERM:
    case ENETUNREACH:
    case EHOSTUNREACH:
    case ENETDOWN:
    case ETIMEDOUT:
      return true;
    default:
      return false;
  }
}

}  // namespace

//-----------------------------------------------------------------------------

TCPClientSocketLibevent::TCPClientSocketLibevent(const AddressList& addresses)
    : socket_(kInvalidSocket),
      addresses_(addresses),
      current_ai_(addresses_.head()),
      waiting_connect_(false),
      read_watcher_(this),
      write_watcher_(this),
      read_callback_(NULL),
      write_callback_(NULL) {
}

TCPClientSocketLibevent::~TCPClientSocketLibevent() {
  Disconnect();
}

int TCPClientSocketLibevent::Connect(CompletionCallback* callback,
                                     LoadLog* load_log) {
  // If already connected, then just return OK.
  if (socket_ != kInvalidSocket)
    return OK;

  DCHECK(!waiting_connect_);
  DCHECK(!load_log_);

  TRACE_EVENT_BEGIN("socket.connect", this, "");

  LoadLog::BeginEvent(load_log, LoadLog::TYPE_TCP_CONNECT);

  int rv = DoConnect();

  if (rv == ERR_IO_PENDING) {
    // Synchronous operation not supported.
    DCHECK(callback);

    load_log_ = load_log;
    waiting_connect_ = true;
    write_callback_ = callback;
  } else {
    TRACE_EVENT_END("socket.connect", this, "");
    LoadLog::EndEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
  }

  return rv;
}

int TCPClientSocketLibevent::DoConnect() {
  while (true) {
    DCHECK(current_ai_);

    int rv = CreateSocket(current_ai_);
    if (rv != OK)
      return rv;

    if (!HANDLE_EINTR(connect(socket_, current_ai_->ai_addr,
                              static_cast<int>(current_ai_->ai_addrlen)))) {
      // Connected without waiting!
      return OK;
    }

    int os_error = errno;
    if (os_error == EINPROGRESS)
      break;

    close(socket_);
    socket_ = kInvalidSocket;

    if (current_ai_->ai_next && ShouldTryNextAddress(os_error)) {
      // connect() can fail synchronously for an address even on a
      // non-blocking socket.  As an example, this can happen when there is
      // no route to the host.  Retry using the next address in the list.
      current_ai_ = current_ai_->ai_next;
    } else {
      DLOG(INFO) << "connect failed: " << os_error;
      return MapConnectError(os_error);
    }
  }

  // Initialize write_socket_watcher_ and link it to our MessagePump.
  // POLLOUT is set if the connection is established.
  // POLLIN is set if the connection fails.
  if (!MessageLoopForIO::current()->WatchFileDescriptor(
          socket_, true, MessageLoopForIO::WATCH_WRITE, &write_socket_watcher_,
          &write_watcher_)) {
    DLOG(INFO) << "WatchFileDescriptor failed: " << errno;
    close(socket_);
    socket_ = kInvalidSocket;
    return MapPosixError(errno);
  }

  return ERR_IO_PENDING;
}

void TCPClientSocketLibevent::Disconnect() {
  if (socket_ == kInvalidSocket)
    return;

  TRACE_EVENT_INSTANT("socket.disconnect", this, "");

  bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
  DCHECK(ok);
  ok = write_socket_watcher_.StopWatchingFileDescriptor();
  DCHECK(ok);
  close(socket_);
  socket_ = kInvalidSocket;
  waiting_connect_ = false;

  // Reset for next time.
  current_ai_ = addresses_.head();
}

bool TCPClientSocketLibevent::IsConnected() const {
  if (socket_ == kInvalidSocket || waiting_connect_)
    return false;

  // Check if connection is alive.
  char c;
  int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK));
  if (rv == 0)
    return false;
  if (rv == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
    return false;

  return true;
}

bool TCPClientSocketLibevent::IsConnectedAndIdle() const {
  if (socket_ == kInvalidSocket || waiting_connect_)
    return false;

  // Check if connection is alive and we haven't received any data
  // unexpectedly.
  char c;
  int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK));
  if (rv >= 0)
    return false;
  if (errno != EAGAIN && errno != EWOULDBLOCK)
    return false;

  return true;
}

int TCPClientSocketLibevent::Read(IOBuffer* buf,
                                  int buf_len,
                                  CompletionCallback* callback) {
  DCHECK_NE(kInvalidSocket, socket_);
  DCHECK(!waiting_connect_);
  DCHECK(!read_callback_);
  // Synchronous operation not supported
  DCHECK(callback);
  DCHECK_GT(buf_len, 0);

  TRACE_EVENT_BEGIN("socket.read", this, "");
  int nread = HANDLE_EINTR(read(socket_, buf->data(), buf_len));
  if (nread >= 0) {
    TRACE_EVENT_END("socket.read", this, StringPrintf("%d bytes", nread));
    return nread;
  }
  if (errno != EAGAIN && errno != EWOULDBLOCK) {
    DLOG(INFO) << "read failed, errno " << errno;
    return MapPosixError(errno);
  }

  if (!MessageLoopForIO::current()->WatchFileDescriptor(
          socket_, true, MessageLoopForIO::WATCH_READ,
          &read_socket_watcher_, &read_watcher_)) {
    DLOG(INFO) << "WatchFileDescriptor failed on read, errno " << errno;
    return MapPosixError(errno);
  }

  read_buf_ = buf;
  read_buf_len_ = buf_len;
  read_callback_ = callback;
  return ERR_IO_PENDING;
}

int TCPClientSocketLibevent::Write(IOBuffer* buf,
                                   int buf_len,
                                   CompletionCallback* callback) {
  DCHECK_NE(kInvalidSocket, socket_);
  DCHECK(!waiting_connect_);
  DCHECK(!write_callback_);
  // Synchronous operation not supported
  DCHECK(callback);
  DCHECK_GT(buf_len, 0);

  TRACE_EVENT_BEGIN("socket.write", this, "");
  int nwrite = HANDLE_EINTR(write(socket_, buf->data(), buf_len));
  if (nwrite >= 0) {
    TRACE_EVENT_END("socket.write", this, StringPrintf("%d bytes", nwrite));
    return nwrite;
  }
  if (errno != EAGAIN && errno != EWOULDBLOCK)
    return MapPosixError(errno);

  if (!MessageLoopForIO::current()->WatchFileDescriptor(
          socket_, true, MessageLoopForIO::WATCH_WRITE,
          &write_socket_watcher_, &write_watcher_)) {
    DLOG(INFO) << "WatchFileDescriptor failed on write, errno " << errno;
    return MapPosixError(errno);
  }

  write_buf_ = buf;
  write_buf_len_ = buf_len;
  write_callback_ = callback;
  return ERR_IO_PENDING;
}

bool TCPClientSocketLibevent::SetReceiveBufferSize(int32 size) {
  int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF,
      reinterpret_cast<const char*>(&size),
      sizeof(size));
  DCHECK(!rv) << "Could not set socket receive buffer size: " << errno;
  return rv == 0;
}

bool TCPClientSocketLibevent::SetSendBufferSize(int32 size) {
  int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
      reinterpret_cast<const char*>(&size),
      sizeof(size));
  DCHECK(!rv) << "Could not set socket send buffer size: " << errno;
  return rv == 0;
}

int TCPClientSocketLibevent::CreateSocket(const addrinfo* ai) {
  socket_ = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
  if (socket_ == kInvalidSocket)
    return MapPosixError(errno);

  if (SetNonBlocking(socket_))
    return MapPosixError(errno);

  return OK;
}

void TCPClientSocketLibevent::DoReadCallback(int rv) {
  DCHECK_NE(rv, ERR_IO_PENDING);
  DCHECK(read_callback_);

  // since Run may result in Read being called, clear read_callback_ up front.
  CompletionCallback* c = read_callback_;
  read_callback_ = NULL;
  c->Run(rv);
}

void TCPClientSocketLibevent::DoWriteCallback(int rv) {
  DCHECK_NE(rv, ERR_IO_PENDING);
  DCHECK(write_callback_);

  // since Run may result in Write being called, clear write_callback_ up front.
  CompletionCallback* c = write_callback_;
  write_callback_ = NULL;
  c->Run(rv);
}

void TCPClientSocketLibevent::DidCompleteConnect() {
  int result = ERR_UNEXPECTED;

  // Check to see if connect succeeded
  int os_error = 0;
  socklen_t len = sizeof(os_error);
  if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &os_error, &len) < 0)
    os_error = errno;

  if (os_error == EINPROGRESS || os_error == EALREADY) {
    NOTREACHED();  // This indicates a bug in libevent or our code.
    result = ERR_IO_PENDING;
  } else if (current_ai_->ai_next && ShouldTryNextAddress(os_error)) {
    // This address failed, try next one in list.
    const addrinfo* next = current_ai_->ai_next;
    Disconnect();
    current_ai_ = next;
    scoped_refptr<LoadLog> load_log;
    load_log.swap(load_log_);
    TRACE_EVENT_END("socket.connect", this, "");
    LoadLog::EndEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
    result = Connect(write_callback_, load_log);
  } else {
    result = MapConnectError(os_error);
    bool ok = write_socket_watcher_.StopWatchingFileDescriptor();
    DCHECK(ok);
    waiting_connect_ = false;
    TRACE_EVENT_END("socket.connect", this, "");
    LoadLog::EndEvent(load_log_, LoadLog::TYPE_TCP_CONNECT);
    load_log_ = NULL;
  }

  if (result != ERR_IO_PENDING) {
    DoWriteCallback(result);
  }
}

void TCPClientSocketLibevent::DidCompleteRead() {
  int bytes_transferred;
  bytes_transferred = HANDLE_EINTR(read(socket_, read_buf_->data(),
                                        read_buf_len_));

  int result;
  if (bytes_transferred >= 0) {
    TRACE_EVENT_END("socket.read", this,
                    StringPrintf("%d bytes", bytes_transferred));
    result = bytes_transferred;
  } else {
    result = MapPosixError(errno);
  }

  if (result != ERR_IO_PENDING) {
    read_buf_ = NULL;
    read_buf_len_ = 0;
    bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
    DCHECK(ok);
    DoReadCallback(result);
  }
}

void TCPClientSocketLibevent::DidCompleteWrite() {
  int bytes_transferred;
  bytes_transferred = HANDLE_EINTR(write(socket_, write_buf_->data(),
                                         write_buf_len_));

  int result;
  if (bytes_transferred >= 0) {
    result = bytes_transferred;
    TRACE_EVENT_END("socket.write", this,
                    StringPrintf("%d bytes", bytes_transferred));
  } else {
    result = MapPosixError(errno);
  }

  if (result != ERR_IO_PENDING) {
    write_buf_ = NULL;
    write_buf_len_ = 0;
    write_socket_watcher_.StopWatchingFileDescriptor();
    DoWriteCallback(result);
  }
}

int TCPClientSocketLibevent::GetPeerName(struct sockaddr* name,
                                         socklen_t* namelen) {
  return ::getpeername(socket_, name, namelen);
}

}  // namespace net
<|endoftext|>


<commit_before>#include "importer.h"
#include "platform.h"
#include "scene/sceneLoader.h"
#include "yaml-cpp/yaml.h"
#include "log.h"

#include <regex>

using YAML::Node;
using YAML::NodeType;

namespace Tangram {

std::atomic_uint Importer::progressCounter(0);

Node Importer::applySceneImports(const Url& scenePath, const Url& resourceRoot) {

    Url path;
    Url rootScenePath = scenePath.resolved(resourceRoot);

    m_sceneQueue.push_back(rootScenePath);

    while (true) {
        {
            std::unique_lock<std::mutex> lock(sceneMutex);

            m_condition.wait(lock, [&, this]{
                    if (m_sceneQueue.empty()) {
                        // Not busy at all?
                        if (progressCounter == 0) { return true; }
                    } else {
                        // More work and not completely busy?
                        if (progressCounter < MAX_SCENE_DOWNLOAD) { return true; }
                    }

                    return false;
                });

            if (m_sceneQueue.empty()) {
                if (progressCounter == 0) {
                    break;
                }
                continue;
            }

            path = m_sceneQueue.back();
            m_sceneQueue.pop_back();

            if (m_scenes.find(path) != m_scenes.end()) { continue; }
        }

        if (path.hasHttpScheme()) {
            progressCounter++;
            startUrlRequest(path.string(), [&, path](std::vector<char>&& rawData) {
                if (!rawData.empty()) {
                    std::unique_lock<std::mutex> lock(sceneMutex);
                    processScene(path, std::string(rawData.data(), rawData.size()));
                }
                progressCounter--;
                m_condition.notify_all();
            });
        } else {
            std::unique_lock<std::mutex> lock(sceneMutex);
            processScene(path, getSceneString(path));
        }
    }

    Node root = Node();

    LOGD("Processing scene import Stack:");
    std::vector<Url> sceneStack;
    importScenesRecursive(root, rootScenePath, sceneStack);

    return root;
}

void Importer::processScene(const Url& scenePath, const std::string &sceneString) {

    LOGD("Process: '%s'", scenePath.string().c_str());

    // Don't load imports twice
    if (m_scenes.find(scenePath) != m_scenes.end()) {
        return;
    }

    try {
        auto sceneNode = YAML::Load(sceneString);

        m_scenes[scenePath] = sceneNode;

        for (const auto& import : getResolvedImportUrls(sceneNode, scenePath)) {
            m_sceneQueue.push_back(import);
            m_condition.notify_all();
        }
    } catch (YAML::ParserException e) {
        LOGE("Parsing scene config '%s'", e.what());
    }
}

bool nodeIsPotentialUrl(const Node& node) {
    // Check that the node is scalar and not null.
    if (node.IsNull() || !node.IsScalar()) { return false; }

    // Check that the node does not contain a 'global' reference.
    if (node.Scalar().compare(0, 7, "global.") == 0) { return false; }

    return true;
}

bool nodeIsTextureUrl(const Node& node, const Node& textures) {

    if (!nodeIsPotentialUrl(node)) { return false; }

    // Check that the node is not a number or a boolean.
    bool booleanValue = false;
    double numberValue = 0.;
    if (YAML::convert<bool>::decode(node, booleanValue)) { return false; }
    if (YAML::convert<double>::decode(node, numberValue)) { return false; }

    // Check that the node does not name a scene texture.
    if (textures[node.Scalar()]) { return false; }

    return true;
}

void Importer::resolveSceneUrls(Node& root, const Url& base) {

    // Resolve global texture URLs.

    Node textures = root["textures"];

    if (textures) {
        for (auto texture : textures) {
            if (Node textureUrlNode = texture.second["url"]) {
                if (nodeIsPotentialUrl(textureUrlNode)) {
                    textureUrlNode = Url(textureUrlNode.Scalar()).resolved(base).string();
                }
            }
        }
    }

    // Resolve inline texture URLs.

    if (Node styles = root["styles"]) {

        for (auto entry : styles) {

            Node style = entry.second;
            if (!style.IsMap()) { continue; }

            //style->texture
            if (Node texture = style["texture"]) {
                if (nodeIsTextureUrl(texture, textures)) {
                    texture = Url(texture.Scalar()).resolved(base).string();
                }
            }

            //style->material->texture
            if (Node material = style["material"]) {
                if (!material.IsMap()) { continue; }
                for (auto& prop : {"emission", "ambient", "diffuse", "specular", "normal"}) {
                    if (Node propNode = material[prop]) {
                        if (!propNode.IsMap()) { continue; }
                        if (Node matTexture = propNode["texture"]) {
                            if (nodeIsTextureUrl(matTexture, textures)) {
                                matTexture = Url(matTexture.Scalar()).resolved(base).string();
                            }
                        }
                    }
                }
            }

            //style->shader->uniforms->texture
            if (Node shaders = style["shaders"]) {
                if (!shaders.IsMap()) { continue; }
                if (Node uniforms = shaders["uniforms"]) {
                    for (auto uniformEntry : uniforms) {
                        Node uniformValue = uniformEntry.second;
                        if (nodeIsTextureUrl(uniformValue, textures)) {
                            uniformValue = Url(uniformValue.Scalar()).resolved(base).string();
                        } else if (uniformValue.IsSequence()) {
                            for (Node u : uniformValue) {
                                if (nodeIsTextureUrl(u, textures)) {
                                    u = Url(u.Scalar()).resolved(base).string();
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // Resolve data source URLs.

    if (Node sources = root["sources"]) {
        for (auto source : sources) {
            if (!source.second.IsMap()) { continue; }
            if (Node sourceUrl = source.second["url"]) {
                if (nodeIsPotentialUrl(sourceUrl)) {
                    sourceUrl = Url(sourceUrl.Scalar()).resolved(base).string();
                }
            }
        }
    }

    // Resolve font URLs.

    if (Node fonts = root["fonts"]) {
        if (fonts.IsMap()) {
            for (const auto& font : fonts) {
                if (font.second.IsMap()) {
                    auto urlNode = font.second["url"];
                    if (nodeIsPotentialUrl(urlNode)) {
                        urlNode = Url(urlNode.Scalar()).resolved(base).string();
                    }
                } else if (font.second.IsSequence()) {
                    for (auto& fontNode : font.second) {
                        auto urlNode = fontNode["url"];
                        if (nodeIsPotentialUrl(urlNode)) {
                            urlNode = Url(urlNode.Scalar()).resolved(base).string();
                        }
                    }
                }
            }
        }
    }
}

std::string Importer::getSceneString(const Url& scenePath) {
    return stringFromFile(scenePath.string().c_str());
}

std::vector<Url> Importer::getResolvedImportUrls(const Node& scene, const Url& base) {

    std::vector<Url> scenePaths;

    if (const Node& import = scene["import"]) {
        if (import.IsScalar()) {
            scenePaths.push_back(Url(import.Scalar()).resolved(base));
        } else if (import.IsSequence()) {
            for (const auto& path : import) {
                if (path.IsScalar()) {
                    scenePaths.push_back(Url(path.Scalar()).resolved(base));
                }
            }
        }
    }

    return scenePaths;
}

void Importer::importScenesRecursive(Node& root, const Url& scenePath, std::vector<Url>& sceneStack) {

    LOGD("Starting importing Scene: %s", scenePath.string().c_str());

    for (const auto& s : sceneStack) {
        if (scenePath == s) {
            LOGE("%s will cause a cyclic import. Stopping this scene from being imported", scenePath.string().c_str());
            return;
        }
    }

    sceneStack.push_back(scenePath);

    auto sceneNode = m_scenes[scenePath];

    if (sceneNode.IsNull()) { return; }
    if (!sceneNode.IsMap()) { return; }

    auto imports = getResolvedImportUrls(sceneNode, scenePath);

    // Don't want to merge imports, so remove them here.
    sceneNode.remove("import");

    for (const auto& url : imports) {

        importScenesRecursive(root, url, sceneStack);

    }

    sceneStack.pop_back();

    mergeMapFields(root, sceneNode);

    resolveSceneUrls(root, scenePath);
}

void Importer::mergeMapFields(Node& target, const Node& import) {

    for (const auto& entry : import) {

        const auto &key = entry.first.Scalar();

        if (!target[key]) {
            target[key] = entry.second;
            continue;
        }

        switch(entry.second.Type()) {
            case NodeType::Scalar:
            case NodeType::Sequence:
                target[key] = entry.second;
                break;
            case NodeType::Map: {
                Node newTarget = target[key];
                mergeMapFields(newTarget, entry.second);
                break;
            }
            default:
                break;
        }
    }
}

}
<commit_msg>do not allow to merge map with non-map nodes<commit_after>#include "importer.h"
#include "platform.h"
#include "scene/sceneLoader.h"
#include "yaml-cpp/yaml.h"
#include "log.h"

#include <regex>

using YAML::Node;
using YAML::NodeType;

namespace Tangram {

std::atomic_uint Importer::progressCounter(0);

Node Importer::applySceneImports(const Url& scenePath, const Url& resourceRoot) {

    Url path;
    Url rootScenePath = scenePath.resolved(resourceRoot);

    m_sceneQueue.push_back(rootScenePath);

    while (true) {
        {
            std::unique_lock<std::mutex> lock(sceneMutex);

            m_condition.wait(lock, [&, this]{
                    if (m_sceneQueue.empty()) {
                        // Not busy at all?
                        if (progressCounter == 0) { return true; }
                    } else {
                        // More work and not completely busy?
                        if (progressCounter < MAX_SCENE_DOWNLOAD) { return true; }
                    }

                    return false;
                });

            if (m_sceneQueue.empty()) {
                if (progressCounter == 0) {
                    break;
                }
                continue;
            }

            path = m_sceneQueue.back();
            m_sceneQueue.pop_back();

            if (m_scenes.find(path) != m_scenes.end()) { continue; }
        }

        if (path.hasHttpScheme()) {
            progressCounter++;
            startUrlRequest(path.string(), [&, path](std::vector<char>&& rawData) {
                if (!rawData.empty()) {
                    std::unique_lock<std::mutex> lock(sceneMutex);
                    processScene(path, std::string(rawData.data(), rawData.size()));
                }
                progressCounter--;
                m_condition.notify_all();
            });
        } else {
            std::unique_lock<std::mutex> lock(sceneMutex);
            processScene(path, getSceneString(path));
        }
    }

    Node root = Node();

    LOGD("Processing scene import Stack:");
    std::vector<Url> sceneStack;
    importScenesRecursive(root, rootScenePath, sceneStack);

    return root;
}

void Importer::processScene(const Url& scenePath, const std::string &sceneString) {

    LOGD("Process: '%s'", scenePath.string().c_str());

    // Don't load imports twice
    if (m_scenes.find(scenePath) != m_scenes.end()) {
        return;
    }

    try {
        auto sceneNode = YAML::Load(sceneString);

        m_scenes[scenePath] = sceneNode;

        for (const auto& import : getResolvedImportUrls(sceneNode, scenePath)) {
            m_sceneQueue.push_back(import);
            m_condition.notify_all();
        }
    } catch (YAML::ParserException e) {
        LOGE("Parsing scene config '%s'", e.what());
    }
}

bool nodeIsPotentialUrl(const Node& node) {
    // Check that the node is scalar and not null.
    if (node.IsNull() || !node.IsScalar()) { return false; }

    // Check that the node does not contain a 'global' reference.
    if (node.Scalar().compare(0, 7, "global.") == 0) { return false; }

    return true;
}

bool nodeIsTextureUrl(const Node& node, const Node& textures) {

    if (!nodeIsPotentialUrl(node)) { return false; }

    // Check that the node is not a number or a boolean.
    bool booleanValue = false;
    double numberValue = 0.;
    if (YAML::convert<bool>::decode(node, booleanValue)) { return false; }
    if (YAML::convert<double>::decode(node, numberValue)) { return false; }

    // Check that the node does not name a scene texture.
    if (textures[node.Scalar()]) { return false; }

    return true;
}

void Importer::resolveSceneUrls(Node& root, const Url& base) {

    // Resolve global texture URLs.

    Node textures = root["textures"];

    if (textures) {
        for (auto texture : textures) {
            if (Node textureUrlNode = texture.second["url"]) {
                if (nodeIsPotentialUrl(textureUrlNode)) {
                    textureUrlNode = Url(textureUrlNode.Scalar()).resolved(base).string();
                }
            }
        }
    }

    // Resolve inline texture URLs.

    if (Node styles = root["styles"]) {

        for (auto entry : styles) {

            Node style = entry.second;
            if (!style.IsMap()) { continue; }

            //style->texture
            if (Node texture = style["texture"]) {
                if (nodeIsTextureUrl(texture, textures)) {
                    texture = Url(texture.Scalar()).resolved(base).string();
                }
            }

            //style->material->texture
            if (Node material = style["material"]) {
                if (!material.IsMap()) { continue; }
                for (auto& prop : {"emission", "ambient", "diffuse", "specular", "normal"}) {
                    if (Node propNode = material[prop]) {
                        if (!propNode.IsMap()) { continue; }
                        if (Node matTexture = propNode["texture"]) {
                            if (nodeIsTextureUrl(matTexture, textures)) {
                                matTexture = Url(matTexture.Scalar()).resolved(base).string();
                            }
                        }
                    }
                }
            }

            //style->shader->uniforms->texture
            if (Node shaders = style["shaders"]) {
                if (!shaders.IsMap()) { continue; }
                if (Node uniforms = shaders["uniforms"]) {
                    for (auto uniformEntry : uniforms) {
                        Node uniformValue = uniformEntry.second;
                        if (nodeIsTextureUrl(uniformValue, textures)) {
                            uniformValue = Url(uniformValue.Scalar()).resolved(base).string();
                        } else if (uniformValue.IsSequence()) {
                            for (Node u : uniformValue) {
                                if (nodeIsTextureUrl(u, textures)) {
                                    u = Url(u.Scalar()).resolved(base).string();
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // Resolve data source URLs.

    if (Node sources = root["sources"]) {
        for (auto source : sources) {
            if (!source.second.IsMap()) { continue; }
            if (Node sourceUrl = source.second["url"]) {
                if (nodeIsPotentialUrl(sourceUrl)) {
                    sourceUrl = Url(sourceUrl.Scalar()).resolved(base).string();
                }
            }
        }
    }

    // Resolve font URLs.

    if (Node fonts = root["fonts"]) {
        if (fonts.IsMap()) {
            for (const auto& font : fonts) {
                if (font.second.IsMap()) {
                    auto urlNode = font.second["url"];
                    if (nodeIsPotentialUrl(urlNode)) {
                        urlNode = Url(urlNode.Scalar()).resolved(base).string();
                    }
                } else if (font.second.IsSequence()) {
                    for (auto& fontNode : font.second) {
                        auto urlNode = fontNode["url"];
                        if (nodeIsPotentialUrl(urlNode)) {
                            urlNode = Url(urlNode.Scalar()).resolved(base).string();
                        }
                    }
                }
            }
        }
    }
}

std::string Importer::getSceneString(const Url& scenePath) {
    return stringFromFile(scenePath.string().c_str());
}

std::vector<Url> Importer::getResolvedImportUrls(const Node& scene, const Url& base) {

    std::vector<Url> scenePaths;

    if (const Node& import = scene["import"]) {
        if (import.IsScalar()) {
            scenePaths.push_back(Url(import.Scalar()).resolved(base));
        } else if (import.IsSequence()) {
            for (const auto& path : import) {
                if (path.IsScalar()) {
                    scenePaths.push_back(Url(path.Scalar()).resolved(base));
                }
            }
        }
    }

    return scenePaths;
}

void Importer::importScenesRecursive(Node& root, const Url& scenePath, std::vector<Url>& sceneStack) {

    LOGD("Starting importing Scene: %s", scenePath.string().c_str());

    for (const auto& s : sceneStack) {
        if (scenePath == s) {
            LOGE("%s will cause a cyclic import. Stopping this scene from being imported", scenePath.string().c_str());
            return;
        }
    }

    sceneStack.push_back(scenePath);

    auto sceneNode = m_scenes[scenePath];

    if (sceneNode.IsNull()) { return; }
    if (!sceneNode.IsMap()) { return; }

    auto imports = getResolvedImportUrls(sceneNode, scenePath);

    // Don't want to merge imports, so remove them here.
    sceneNode.remove("import");

    for (const auto& url : imports) {

        importScenesRecursive(root, url, sceneStack);

    }

    sceneStack.pop_back();

    mergeMapFields(root, sceneNode);

    resolveSceneUrls(root, scenePath);
}

void Importer::mergeMapFields(Node& target, const Node& import) {

    for (const auto& entry : import) {

        const auto &key = entry.first.Scalar();

        if (!target[key]) {
            target[key] = entry.second;
            continue;
        }

        if (target[key].Type() != entry.second.Type()) {
            LOGN("Merging different node types: '%s'\n'%s'\n<==\n'%s'",
                 key.c_str(), Dump(target[key]).c_str(), Dump(entry.second).c_str());
        }

        switch(target[key].Type()) {
            case NodeType::Scalar:
            case NodeType::Sequence:
                target[key] = entry.second;
                break;

            case NodeType::Map: {
                auto newTarget = target[key];
                if (entry.second.IsMap()) {
                    mergeMapFields(newTarget, entry.second);
                } else {
                    target[key] = entry.second;
                }
                break;
            }
            default:
                break;
        }
    }
}

}
<|endoftext|>


<commit_before>#ifndef INDEX_GEOUTILS_HPP_DEFINED
#define INDEX_GEOUTILS_HPP_DEFINED

#include "BoundingBox.hpp"
#include "QuadKey.hpp"
#include "utils/MathUtils.hpp"

#include <algorithm>
#include <cmath>
#include <string>

namespace utymap { namespace utils {

class GeoUtils
{
public:
    static const int MinLevelOfDetails = 1;
    static const int MaxLevelOfDetails = 19;

    // Converts Latitude/Longitude to quadkey
    static QuadKey latLonToQuadKey(const GeoCoordinate& coordinate, int levelOfDetail)
    {
        QuadKey quadKey;
        quadKey.tileX = lonToTileX(clamp(coordinate.longitude, -179.9999999, 179.9999999), levelOfDetail);
        quadKey.tileY = latToTileY(clamp(coordinate.latitude, -85.05112877, 85.05112877), levelOfDetail);
        quadKey.levelOfDetail = levelOfDetail;
        return quadKey;
    }

    // Converts quadkey to bounding box
    static BoundingBox quadKeyToBoundingBox(const QuadKey& quadKey)
    {
        GeoCoordinate minPoint, maxPoint;
        int levelOfDetail = quadKey.levelOfDetail;
        minPoint.latitude = tileYToLat(quadKey.tileY + 1, levelOfDetail);
        maxPoint.latitude = tileYToLat(quadKey.tileY, levelOfDetail);
        minPoint.longitude = tileXToLon(quadKey.tileX, levelOfDetail);
        maxPoint.longitude = tileXToLon(quadKey.tileX + 1, levelOfDetail);

        return BoundingBox(minPoint, maxPoint);
    }

    static std::string quadKeyToString(const QuadKey& quadKey)
    {
        std::string code;
        code.reserve(quadKey.levelOfDetail);
        for (int i = quadKey.levelOfDetail; i > 0; --i)
        {
            char digit = '0';
            int mask = 1 << (i - 1);
            if ((quadKey.tileX & mask) != 0)
                digit++;
            if ((quadKey.tileY & mask) != 0)
            {
                digit++;
                digit++;
            }
            code += digit;
        }
        return std::move(code);
    }

    // Visits all tiles which are intersecting with given bounding box at given level of details
    template<typename Visitor>
    static void visitTileRange(const BoundingBox& bbox, int levelOfDetail, const Visitor& visitor)
    {
        if (!bbox.isValid()) return;

        QuadKey start = latLonToQuadKey(bbox.minPoint, levelOfDetail);
        QuadKey end = latLonToQuadKey(bbox.maxPoint, levelOfDetail);

        for (int y = end.tileY; y < start.tileY + 1; y++) {
            for (int x = start.tileX; x < end.tileX + 1; x++) {
                const QuadKey currentQuadKey = { levelOfDetail, x, y };
                const BoundingBox currentBbox = quadKeyToBoundingBox(currentQuadKey);
                if (bbox.intersects(currentBbox)) {
                    visitor(currentQuadKey, currentBbox);
                }
            }
        }
    }

    // Checks whether given point inside polygon.
    template <typename Iter>
    static bool isPointInPolygon(const GeoCoordinate& point, Iter begin, Iter end)
    {
        bool c = false;
        for (auto iCoord = begin, jCoord = end - 1; iCoord != end; jCoord = iCoord++) {
            if (((iCoord->latitude > point.latitude) != (jCoord->latitude > point.latitude)) &&
                (point.longitude < (jCoord->longitude - iCoord->longitude) * (point.latitude - iCoord->latitude) /
                (jCoord->latitude - iCoord->latitude) + iCoord->longitude))
                c = !c;
        }
        return c;
    }

    // gets offset in meters
    static double getOffset(const GeoCoordinate& point, double offsetInMeters)
    {
        double lat = deg2Rad(point.latitude);
        // Radius of Earth at given latitude
        double radius = wgs84EarthRadius(lat);
        double dWidth = offsetInMeters / (2 * radius);
        return rad2Deg(dWidth);
    }

    // Gets distance in meters for given coordinate
    static double distance(const GeoCoordinate& p1, const GeoCoordinate& p2)
    {
        double dLat = deg2Rad(p1.latitude - p1.latitude);
        double dLon = deg2Rad(p1.longitude - p1.longitude);

        double lat1 = deg2Rad(p1.latitude);
        double lat2 = deg2Rad(p2.latitude);

        double a = std::sin(dLat / 2) * std::sin(dLat / 2) +
            std::sin(dLon / 2) * std::sin(dLon / 2) * std::cos(lat1) * std::cos(lat2);

        double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1 - a));

        double radius = wgs84EarthRadius(dLat);

        return radius * c;
    }

private:

    inline static int lonToTileX(double lon, int levelOfDetail)
    {
        return (int)(std::floor((lon + 180.0) / 360.0 * pow(2.0, levelOfDetail)));
    }

    inline static int latToTileY(double lat, int levelOfDetail)
    {
        return (int)(std::floor((1.0 - log(tan(lat * pi / 180.0) + 1.0 / cos(lat * pi / 180.0)) / pi) /
            2.0 * pow(2.0, levelOfDetail)));
    }

    inline static double tileXToLon(int x, int levelOfDetail)
    {
        return x / pow(2.0, levelOfDetail) * 360.0 - 180;
    }

    inline static double tileYToLat(int y, int levelOfDetail)
    {
        double n = pi - 2.0 * pi * y / pow(2.0, levelOfDetail);
        return 180.0 / pi * atan(0.5 * (exp(n) - exp(-n)));
    }

    /// Earth radius at a given latitude, according to the WGS-84 ellipsoid [m].
    inline static double wgs84EarthRadius(double lat)
    {
        // Semi-axes of WGS-84 geoidal reference
        const double WGS84_a = 6378137.0; // Major semiaxis [m]
        const double WGS84_b = 6356752.3; // Minor semiaxis [m]

        // http://en.wikipedia.org/wiki/Earth_radius
        auto an = WGS84_a * WGS84_a * std::cos(lat);
        auto bn = WGS84_b * WGS84_b * std::sin(lat);
        auto ad = WGS84_a * std::cos(lat);
        auto bd = WGS84_b * std::sin(lat);
        return std::sqrt((an * an + bn * bn) / (ad * ad + bd * bd));
    }
};

}}

#endif // INDEX_GEOUTILS_HPP_DEFINED
<commit_msg>core: fix typo in distance function<commit_after>#ifndef INDEX_GEOUTILS_HPP_DEFINED
#define INDEX_GEOUTILS_HPP_DEFINED

#include "BoundingBox.hpp"
#include "QuadKey.hpp"
#include "utils/MathUtils.hpp"

#include <algorithm>
#include <cmath>
#include <string>

namespace utymap { namespace utils {

class GeoUtils
{
public:
    static const int MinLevelOfDetails = 1;
    static const int MaxLevelOfDetails = 19;

    // Converts Latitude/Longitude to quadkey
    static QuadKey latLonToQuadKey(const GeoCoordinate& coordinate, int levelOfDetail)
    {
        QuadKey quadKey;
        quadKey.tileX = lonToTileX(clamp(coordinate.longitude, -179.9999999, 179.9999999), levelOfDetail);
        quadKey.tileY = latToTileY(clamp(coordinate.latitude, -85.05112877, 85.05112877), levelOfDetail);
        quadKey.levelOfDetail = levelOfDetail;
        return quadKey;
    }

    // Converts quadkey to bounding box
    static BoundingBox quadKeyToBoundingBox(const QuadKey& quadKey)
    {
        GeoCoordinate minPoint, maxPoint;
        int levelOfDetail = quadKey.levelOfDetail;
        minPoint.latitude = tileYToLat(quadKey.tileY + 1, levelOfDetail);
        maxPoint.latitude = tileYToLat(quadKey.tileY, levelOfDetail);
        minPoint.longitude = tileXToLon(quadKey.tileX, levelOfDetail);
        maxPoint.longitude = tileXToLon(quadKey.tileX + 1, levelOfDetail);

        return BoundingBox(minPoint, maxPoint);
    }

    static std::string quadKeyToString(const QuadKey& quadKey)
    {
        std::string code;
        code.reserve(quadKey.levelOfDetail);
        for (int i = quadKey.levelOfDetail; i > 0; --i)
        {
            char digit = '0';
            int mask = 1 << (i - 1);
            if ((quadKey.tileX & mask) != 0)
                digit++;
            if ((quadKey.tileY & mask) != 0)
            {
                digit++;
                digit++;
            }
            code += digit;
        }
        return std::move(code);
    }

    // Visits all tiles which are intersecting with given bounding box at given level of details
    template<typename Visitor>
    static void visitTileRange(const BoundingBox& bbox, int levelOfDetail, const Visitor& visitor)
    {
        if (!bbox.isValid()) return;

        QuadKey start = latLonToQuadKey(bbox.minPoint, levelOfDetail);
        QuadKey end = latLonToQuadKey(bbox.maxPoint, levelOfDetail);

        for (int y = end.tileY; y < start.tileY + 1; y++) {
            for (int x = start.tileX; x < end.tileX + 1; x++) {
                const QuadKey currentQuadKey = { levelOfDetail, x, y };
                const BoundingBox currentBbox = quadKeyToBoundingBox(currentQuadKey);
                if (bbox.intersects(currentBbox)) {
                    visitor(currentQuadKey, currentBbox);
                }
            }
        }
    }

    // Checks whether given point inside polygon.
    template <typename Iter>
    static bool isPointInPolygon(const GeoCoordinate& point, Iter begin, Iter end)
    {
        bool c = false;
        for (auto iCoord = begin, jCoord = end - 1; iCoord != end; jCoord = iCoord++) {
            if (((iCoord->latitude > point.latitude) != (jCoord->latitude > point.latitude)) &&
                (point.longitude < (jCoord->longitude - iCoord->longitude) * (point.latitude - iCoord->latitude) /
                (jCoord->latitude - iCoord->latitude) + iCoord->longitude))
                c = !c;
        }
        return c;
    }

    // gets offset in meters
    static double getOffset(const GeoCoordinate& point, double offsetInMeters)
    {
        double lat = deg2Rad(point.latitude);
        // Radius of Earth at given latitude
        double radius = wgs84EarthRadius(lat);
        double dWidth = offsetInMeters / (2 * radius);
        return rad2Deg(dWidth);
    }

    // Gets distance in meters for given coordinate
    static double distance(const GeoCoordinate& p1, const GeoCoordinate& p2)
    {
        double dLat = deg2Rad(p1.latitude - p2.latitude);
        double dLon = deg2Rad(p1.longitude - p2.longitude);

        double lat1 = deg2Rad(p1.latitude);
        double lat2 = deg2Rad(p2.latitude);

        double a = std::sin(dLat / 2) * std::sin(dLat / 2) +
            std::sin(dLon / 2) * std::sin(dLon / 2) * std::cos(lat1) * std::cos(lat2);

        double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1 - a));

        double radius = wgs84EarthRadius(dLat);

        return radius * c;
    }

private:

    inline static int lonToTileX(double lon, int levelOfDetail)
    {
        return (int)(std::floor((lon + 180.0) / 360.0 * pow(2.0, levelOfDetail)));
    }

    inline static int latToTileY(double lat, int levelOfDetail)
    {
        return (int)(std::floor((1.0 - log(tan(lat * pi / 180.0) + 1.0 / cos(lat * pi / 180.0)) / pi) /
            2.0 * pow(2.0, levelOfDetail)));
    }

    inline static double tileXToLon(int x, int levelOfDetail)
    {
        return x / pow(2.0, levelOfDetail) * 360.0 - 180;
    }

    inline static double tileYToLat(int y, int levelOfDetail)
    {
        double n = pi - 2.0 * pi * y / pow(2.0, levelOfDetail);
        return 180.0 / pi * atan(0.5 * (exp(n) - exp(-n)));
    }

    /// Earth radius at a given latitude, according to the WGS-84 ellipsoid [m].
    inline static double wgs84EarthRadius(double lat)
    {
        // Semi-axes of WGS-84 geoidal reference
        const double WGS84_a = 6378137.0; // Major semiaxis [m]
        const double WGS84_b = 6356752.3; // Minor semiaxis [m]

        // http://en.wikipedia.org/wiki/Earth_radius
        auto an = WGS84_a * WGS84_a * std::cos(lat);
        auto bn = WGS84_b * WGS84_b * std::sin(lat);
        auto ad = WGS84_a * std::cos(lat);
        auto bd = WGS84_b * std::sin(lat);
        return std::sqrt((an * an + bn * bn) / (ad * ad + bd * bd));
    }
};

}}

#endif // INDEX_GEOUTILS_HPP_DEFINED
<|endoftext|>


<commit_before>/* bzflag
 * Copyright (c) 1993 - 2004 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/* common headers */
#include "common.h"

/* interface header */
#include "DXJoystick.h"

// Don't try compile this if we don't have an up-to-date DX
#if defined(USE_DINPUT)

/* system headers */
#include <vector>
#include <string>

/* local impl. headers */
#include "WinWindow.h"
#include "ErrorHandler.h"
#include "TextUtils.h"

std::vector<DIDEVICEINSTANCE> DXJoystick::devices;

DXJoystick::DXJoystick() : device(NULL)
{
  HINSTANCE hinst = GetModuleHandle(NULL);
  HRESULT success = DirectInputCreateEx(hinst, DIRECTINPUT_VERSION, IID_IDirectInput7,
					(void**)&directInput, NULL);

  if (success != DI_OK) {
    DXError("Could not initialize DirectInput", success);
    return;
  }

  enumerateDevices();
}

DXJoystick::~DXJoystick()
{
  // unacquire the joystick
  if (device)
    device->Unacquire();

  // release DX objects
  if (device) {
    device->Release();
    device = NULL;
  }
  if (directInput) {
    directInput->Release();
    directInput = NULL;
  }
}

void	      DXJoystick::initJoystick(const char* joystickName)
{
  // turn it off
  if (strcmp(joystickName, "off") == 0) {
    device = NULL;
    return;
  }

  /*
   * Find this device, and try to initialize it.
   */
  GUID thisDevice;
  for (unsigned int i = 0; i < devices.size(); i++) {
    if (strcmp(joystickName, devices[i].tszProductName) == 0) {
      thisDevice = devices[i].guidInstance;
      break;
    }
  }
  HRESULT success = directInput->CreateDeviceEx(thisDevice,
						IID_IDirectInputDevice7,
						(void**)&device, NULL);

  if (success != DI_OK) {
    DXError("Could not initialize device", success);
    return;
  }

  /*
   * Set device cooperation level - all input on this device goes to BZFlag,
   * because we're greedy.
   */

  success = device->SetCooperativeLevel(WinWindow::getHandle(),
					DISCL_BACKGROUND | DISCL_EXCLUSIVE);

  if (success != DI_OK) {
    // couldn't grab device, what to do now?
    DXError("Could not set exclusive mode", success);
    device = NULL;
    return;
  }

  /*
   * Set the device data format.  We want buttons and axis, so we'll use a
   * predefined Joystick structure.
   */

  success = device->SetDataFormat(&c_dfDIJoystick);

  if (success != DI_OK) {
    // couldn't set data format, what to do now?
    DXError("Could not set data format", success);
    device = NULL;
    return;
  }

  /*
   * Set data ranges for both axes.  This has the side effect
   * of ensuring that the joystick has at least two axes.
   */

  DIPROPRANGE range;
  range.diph.dwSize       = sizeof(range);
  range.diph.dwHeaderSize = sizeof(range.diph);
  range.diph.dwObj	= DIJOFS_X;
  range.diph.dwHow	= DIPH_BYOFFSET;
  range.lMin	      = -1000;
  range.lMax	      = +1000;

  success = device->SetProperty(DIPROP_RANGE, &range.diph);

  if (success != DI_OK) {
    // couldn't set x axis range, what to do now?
    DXError("Could not set X-axis range", success);
    device = NULL;
    return;
  }

  range.diph.dwObj	  = DIJOFS_Y;

  success = device->SetProperty(DIPROP_RANGE, &range.diph);

  if (success != DI_OK) {
    // couldn't set y axis range, what to do now?
    DXError("Could not set Y-axis range", success);
    device = NULL;
    return;
  }

  /*
   * Acquire the device so that we can get input from it.
   */

  reaquireDevice();
}

bool	      DXJoystick::joystick() const
{
  return (device != NULL);
}

void	      DXJoystick::getJoy(int& x, int& y)
{
  if (!device) return;

  DIJOYSTATE state = pollDevice();

  x = state.lX;
  y = state.lY;

  // ballistics
  x = (x * abs(x)) / 1000;
  y = (y * abs(y)) / 1000;

  return;
}

unsigned long DXJoystick::getJoyButtons()
{
  if (!device) return 0;

  DIJOYSTATE state = pollDevice();

  unsigned long buttons = 0;

  for (int i = 0; i < 32; i++) {
    if (state.rgbButtons[i] & 0x80)
      buttons |= (1 << i);
  }

  return buttons;
}

DIJOYSTATE    DXJoystick::pollDevice()
{
  DIJOYSTATE state;

  HRESULT success = device->Poll();
  // umm, ignore that result...yeah

  success = device->GetDeviceState(sizeof(DIJOYSTATE), &state);
  if (success != DI_OK) {
    // got no state, what's wrong?
    DXError("Acquisition succeeded, but could not get joystick status", success);
  }

  return state;
}

void	      DXJoystick::getJoyDevices(std::vector<std::string> &list) const
{
  for (unsigned int i = 0; i < devices.size(); i++) {
    list.push_back(devices[i].tszProductName);
  }
}

/*
 * Cool force feedback functions.
 */

bool	      DXJoystick::ffHasRumble() const
{
  if (!device)
    return false;

  DIDEVCAPS caps;
  caps.dwSize = sizeof(DIDEVCAPS);

  HRESULT success = device->GetCapabilities(&caps);

  if (success != DI_OK) {
    // couldn't get capabilities, assume no force feedback
    printError("Could not get joystick capabilities, assuming no force feedback");
    return false;
  }

  // if we support force feedback, assume we support rumble
  if (caps.dwFlags & DIDC_FORCEFEEDBACK)
    return true;

  return false;
}

void	      DXJoystick::ffRumble(int count, float delay, float duration,
				   float strong_motor, float weak_motor)
{
  if (!device)
    return;

  /*
   * Create a constant "rumbling" effect with the specified parameters
   * Note that on joysticks that support "real" force feedback this will
   * probably just feel like a constant pressure.
   */
  DICONSTANTFORCE constantForce;

  /* This is about consistent with the relative strength of the motors
   * in the Logitech Cordless Rumblepad, which seems to be what the Linux
   * FF_RUMBLE API (which bzflag's is patterned after) was designed for.
   */
  float combined = strong_motor + weak_motor / 2.0f;
  if (combined > 1.0f)
    combined = 1.0f;

  constantForce.lMagnitude = (LONG)(DI_FFNOMINALMAX * combined);

  /*
   * Build the actual effect
   */
  DWORD axes[3] = {DIJOFS_X, DIJOFS_Y, DIJOFS_Z};
  LONG  dir[3] = {1, 1, 1};

  LPDIRECTINPUTEFFECT createdEffect;

  DIEFFECT effect;
  effect.dwSize = sizeof(DIEFFECT);
  // coordinate system really doesn't matter for rumbles but we need to specify it.
  effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
  // duration
  effect.dwDuration = (DWORD)(duration * DI_SECONDS);
  // defaults
  effect.dwSamplePeriod = 0;
  effect.dwGain = DI_FFNOMINALMAX;
  effect.dwTriggerButton = DIEB_NOTRIGGER;
  effect.dwTriggerRepeatInterval = 0;
  // all axes
  effect.cAxes = 3;
  effect.rgdwAxes = &axes[0];
  // direction doesn't matter
  effect.rglDirection = &dir[0];
  // no envelope
  effect.lpEnvelope = NULL;
  // use the constant force data
  effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
  effect.lpvTypeSpecificParams = &constantForce;
  // start delay
  effect.dwStartDelay = (DWORD)(delay * DI_SECONDS);

  // create the effect
  HRESULT success = device->CreateEffect(GUID_ConstantForce, &effect, &createdEffect, NULL);

  if (success != DI_OK) {
    DXError("Could not create rumble effect", success);
    return;
  }

  // play the thing
  success = createdEffect->Start(count, 0);

  if (success != DI_OK) {
    // uh-oh, no worky
    DXError("Could not play rumble effect", success);
  }

  return;
}

void DXJoystick::enumerateDevices()
{
  if (!directInput)
    return;

  devices.clear();

  HRESULT success = directInput->EnumDevices(DIDEVTYPE_JOYSTICK,
					     &deviceEnumCallback, NULL,
					     DIEDFL_ATTACHEDONLY);

  if (success != DI_OK) {
    DXError("Could not enumerate DirectInput devices", success);
    return;
  }
}

void DXJoystick::reaquireDevice()
{
  if (!device)
    return;

  // try to reaquire the device
  HRESULT success = device->Acquire();

  if (success != DI_OK) {
    // couldn't acquire, what to do now?
    device = NULL;
    DXError("Could not acquire device", success);
  }
}

void DXJoystick::resetFF()
{
  if (!device)
    return;

  HRESULT success = device->SendForceFeedbackCommand(DISFFC_RESET);

  if (success != DI_OK) {
    // couldn't reset, what to do now?
    device = NULL;
    DXError("Could not reset force feedback device", success);
  }
}

/* error handling */

void DXJoystick::DXError(const char* situation, HRESULT problem)
{
  // uh-oh, no worky
  char buffer[40] = {0};

  // some stuff we can handle
  if (problem == (HRESULT)DIERR_UNPLUGGED) {
    device = NULL;
    printError("Joystick device in use has been unplugged.");
    enumerateDevices();
    return;
  }
  if (problem == (HRESULT)DIERR_INPUTLOST) {
    reaquireDevice();
    return;
  }
  if (problem == (HRESULT)DIERR_DEVICEFULL) {
    printError("DirectInput device is full.  Resetting FF state.");
    resetFF();
    return;
  }

  // print error messages
  if (problem == (HRESULT)DIERR_DEVICENOTREG)
    sprintf(buffer, "Device not registered");
  else if (problem == (HRESULT)DIERR_INVALIDPARAM)
    sprintf(buffer, "Invalid parameter");
  else if (problem == (HRESULT)DIERR_NOTINITIALIZED)
    sprintf(buffer, "Device not initialized");
  else if (problem == (HRESULT)DI_BUFFEROVERFLOW)
    sprintf(buffer, "Buffer overflow");
  else if (problem == (HRESULT)DIERR_BADDRIVERVER)
    sprintf(buffer, "Bad or incompatible device driver");
  else if (problem == (HRESULT)DIERR_EFFECTPLAYING)
    sprintf(buffer, "Effect already playing");
  else if (problem == (HRESULT)DIERR_INCOMPLETEEFFECT)
    sprintf(buffer, "Incomplete effect");
  else if (problem == (HRESULT)DIERR_MOREDATA)
    sprintf(buffer, "Return buffer not large enough");
  else if (problem == (HRESULT)DIERR_NOTACQUIRED)
    sprintf(buffer, "Device not acquired");
  else if (problem == (HRESULT)DIERR_NOTDOWNLOADED)
    sprintf(buffer, "Effect not downloaded");
  else if (problem == (HRESULT)DIERR_NOTINITIALIZED)
    sprintf(buffer, "Device not initialized");
  else if (problem == (HRESULT)DIERR_OUTOFMEMORY)
    sprintf(buffer, "Out of memory");
  else if (problem == (HRESULT)DIERR_UNSUPPORTED)
    sprintf(buffer, "Action not supported by driver");
  else
    sprintf(buffer, "Unknown error (%d)", (int)problem);
  printError(TextUtils::format("%s (%s).", situation, buffer));
}

/* Nasty callbacks 'cause DirectX sucks */

BOOL CALLBACK DXJoystick::deviceEnumCallback(LPCDIDEVICEINSTANCE device, void* /*pvRef*/)
{
  if (!device)
    return DIENUM_STOP;

  devices.push_back(*device);

  return DIENUM_CONTINUE;
}

#endif

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<commit_msg>yep, the world might have ended had the null pointer gone unchecked *cough*<commit_after>/* bzflag
 * Copyright (c) 1993 - 2004 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/* common headers */
#include "common.h"

/* interface header */
#include "DXJoystick.h"

// Don't try compile this if we don't have an up-to-date DX
#if defined(USE_DINPUT)

/* system headers */
#include <vector>
#include <string>

/* local impl. headers */
#include "WinWindow.h"
#include "ErrorHandler.h"
#include "TextUtils.h"

std::vector<DIDEVICEINSTANCE> DXJoystick::devices;

DXJoystick::DXJoystick() : device(NULL)
{
  HINSTANCE hinst = GetModuleHandle(NULL);
  HRESULT success = DirectInputCreateEx(hinst, DIRECTINPUT_VERSION, IID_IDirectInput7,
					(void**)&directInput, NULL);

  if (success != DI_OK) {
    DXError("Could not initialize DirectInput", success);
    return;
  }

  enumerateDevices();
}

DXJoystick::~DXJoystick()
{
  // unacquire the joystick
  if (device)
    device->Unacquire();

  // release DX objects
  if (device) {
    device->Release();
    device = NULL;
  }
  if (directInput) {
    directInput->Release();
    directInput = NULL;
  }
}

void	      DXJoystick::initJoystick(const char* joystickName)
{
  // turn it off
  if (!joystickName || strcmp(joystickName, "off") == 0) {
    device = NULL;
    return;
  }

  /*
   * Find this device, and try to initialize it.
   */
  GUID thisDevice;
  for (unsigned int i = 0; i < devices.size(); i++) {
    if (strcmp(joystickName, devices[i].tszProductName) == 0) {
      thisDevice = devices[i].guidInstance;
      break;
    }
  }
  HRESULT success = directInput->CreateDeviceEx(thisDevice,
						IID_IDirectInputDevice7,
						(void**)&device, NULL);

  if (success != DI_OK) {
    DXError("Could not initialize device", success);
    return;
  }

  /*
   * Set device cooperation level - all input on this device goes to BZFlag,
   * because we're greedy.
   */

  success = device->SetCooperativeLevel(WinWindow::getHandle(),
					DISCL_BACKGROUND | DISCL_EXCLUSIVE);

  if (success != DI_OK) {
    // couldn't grab device, what to do now?
    DXError("Could not set exclusive mode", success);
    device = NULL;
    return;
  }

  /*
   * Set the device data format.  We want buttons and axis, so we'll use a
   * predefined Joystick structure.
   */

  success = device->SetDataFormat(&c_dfDIJoystick);

  if (success != DI_OK) {
    // couldn't set data format, what to do now?
    DXError("Could not set data format", success);
    device = NULL;
    return;
  }

  /*
   * Set data ranges for both axes.  This has the side effect
   * of ensuring that the joystick has at least two axes.
   */

  DIPROPRANGE range;
  range.diph.dwSize       = sizeof(range);
  range.diph.dwHeaderSize = sizeof(range.diph);
  range.diph.dwObj	= DIJOFS_X;
  range.diph.dwHow	= DIPH_BYOFFSET;
  range.lMin	      = -1000;
  range.lMax	      = +1000;

  success = device->SetProperty(DIPROP_RANGE, &range.diph);

  if (success != DI_OK) {
    // couldn't set x axis range, what to do now?
    DXError("Could not set X-axis range", success);
    device = NULL;
    return;
  }

  range.diph.dwObj	  = DIJOFS_Y;

  success = device->SetProperty(DIPROP_RANGE, &range.diph);

  if (success != DI_OK) {
    // couldn't set y axis range, what to do now?
    DXError("Could not set Y-axis range", success);
    device = NULL;
    return;
  }

  /*
   * Acquire the device so that we can get input from it.
   */

  reaquireDevice();
}

bool	      DXJoystick::joystick() const
{
  return (device != NULL);
}

void	      DXJoystick::getJoy(int& x, int& y)
{
  if (!device) return;

  DIJOYSTATE state = pollDevice();

  x = state.lX;
  y = state.lY;

  // ballistics
  x = (x * abs(x)) / 1000;
  y = (y * abs(y)) / 1000;

  return;
}

unsigned long DXJoystick::getJoyButtons()
{
  if (!device) return 0;

  DIJOYSTATE state = pollDevice();

  unsigned long buttons = 0;

  for (int i = 0; i < 32; i++) {
    if (state.rgbButtons[i] & 0x80)
      buttons |= (1 << i);
  }

  return buttons;
}

DIJOYSTATE    DXJoystick::pollDevice()
{
  DIJOYSTATE state;

  HRESULT success = device->Poll();
  // umm, ignore that result...yeah

  success = device->GetDeviceState(sizeof(DIJOYSTATE), &state);
  if (success != DI_OK) {
    // got no state, what's wrong?
    DXError("Acquisition succeeded, but could not get joystick status", success);
  }

  return state;
}

void	      DXJoystick::getJoyDevices(std::vector<std::string> &list) const
{
  for (unsigned int i = 0; i < devices.size(); i++) {
    list.push_back(devices[i].tszProductName);
  }
}

/*
 * Cool force feedback functions.
 */

bool	      DXJoystick::ffHasRumble() const
{
  if (!device)
    return false;

  DIDEVCAPS caps;
  caps.dwSize = sizeof(DIDEVCAPS);

  HRESULT success = device->GetCapabilities(&caps);

  if (success != DI_OK) {
    // couldn't get capabilities, assume no force feedback
    printError("Could not get joystick capabilities, assuming no force feedback");
    return false;
  }

  // if we support force feedback, assume we support rumble
  if (caps.dwFlags & DIDC_FORCEFEEDBACK)
    return true;

  return false;
}

void	      DXJoystick::ffRumble(int count, float delay, float duration,
				   float strong_motor, float weak_motor)
{
  if (!device)
    return;

  /*
   * Create a constant "rumbling" effect with the specified parameters
   * Note that on joysticks that support "real" force feedback this will
   * probably just feel like a constant pressure.
   */
  DICONSTANTFORCE constantForce;

  /* This is about consistent with the relative strength of the motors
   * in the Logitech Cordless Rumblepad, which seems to be what the Linux
   * FF_RUMBLE API (which bzflag's is patterned after) was designed for.
   */
  float combined = strong_motor + weak_motor / 2.0f;
  if (combined > 1.0f)
    combined = 1.0f;

  constantForce.lMagnitude = (LONG)(DI_FFNOMINALMAX * combined);

  /*
   * Build the actual effect
   */
  DWORD axes[3] = {DIJOFS_X, DIJOFS_Y, DIJOFS_Z};
  LONG  dir[3] = {1, 1, 1};

  LPDIRECTINPUTEFFECT createdEffect;

  DIEFFECT effect;
  effect.dwSize = sizeof(DIEFFECT);
  // coordinate system really doesn't matter for rumbles but we need to specify it.
  effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
  // duration
  effect.dwDuration = (DWORD)(duration * DI_SECONDS);
  // defaults
  effect.dwSamplePeriod = 0;
  effect.dwGain = DI_FFNOMINALMAX;
  effect.dwTriggerButton = DIEB_NOTRIGGER;
  effect.dwTriggerRepeatInterval = 0;
  // all axes
  effect.cAxes = 3;
  effect.rgdwAxes = &axes[0];
  // direction doesn't matter
  effect.rglDirection = &dir[0];
  // no envelope
  effect.lpEnvelope = NULL;
  // use the constant force data
  effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
  effect.lpvTypeSpecificParams = &constantForce;
  // start delay
  effect.dwStartDelay = (DWORD)(delay * DI_SECONDS);

  // create the effect
  HRESULT success = device->CreateEffect(GUID_ConstantForce, &effect, &createdEffect, NULL);

  if (success != DI_OK) {
    DXError("Could not create rumble effect", success);
    return;
  }

  // play the thing
  success = createdEffect->Start(count, 0);

  if (success != DI_OK) {
    // uh-oh, no worky
    DXError("Could not play rumble effect", success);
  }

  return;
}

void DXJoystick::enumerateDevices()
{
  if (!directInput)
    return;

  devices.clear();

  HRESULT success = directInput->EnumDevices(DIDEVTYPE_JOYSTICK,
					     &deviceEnumCallback, NULL,
					     DIEDFL_ATTACHEDONLY);

  if (success != DI_OK) {
    DXError("Could not enumerate DirectInput devices", success);
    return;
  }
}

void DXJoystick::reaquireDevice()
{
  if (!device)
    return;

  // try to reaquire the device
  HRESULT success = device->Acquire();

  if (success != DI_OK) {
    // couldn't acquire, what to do now?
    device = NULL;
    DXError("Could not acquire device", success);
  }
}

void DXJoystick::resetFF()
{
  if (!device)
    return;

  HRESULT success = device->SendForceFeedbackCommand(DISFFC_RESET);

  if (success != DI_OK) {
    // couldn't reset, what to do now?
    device = NULL;
    DXError("Could not reset force feedback device", success);
  }
}

/* error handling */

void DXJoystick::DXError(const char* situation, HRESULT problem)
{
  // uh-oh, no worky
  char buffer[40] = {0};

  // some stuff we can handle
  if (problem == (HRESULT)DIERR_UNPLUGGED) {
    device = NULL;
    printError("Joystick device in use has been unplugged.");
    enumerateDevices();
    return;
  }
  if (problem == (HRESULT)DIERR_INPUTLOST) {
    reaquireDevice();
    return;
  }
  if (problem == (HRESULT)DIERR_DEVICEFULL) {
    printError("DirectInput device is full.  Resetting FF state.");
    resetFF();
    return;
  }

  // print error messages
  if (problem == (HRESULT)DIERR_DEVICENOTREG)
    sprintf(buffer, "Device not registered");
  else if (problem == (HRESULT)DIERR_INVALIDPARAM)
    sprintf(buffer, "Invalid parameter");
  else if (problem == (HRESULT)DIERR_NOTINITIALIZED)
    sprintf(buffer, "Device not initialized");
  else if (problem == (HRESULT)DI_BUFFEROVERFLOW)
    sprintf(buffer, "Buffer overflow");
  else if (problem == (HRESULT)DIERR_BADDRIVERVER)
    sprintf(buffer, "Bad or incompatible device driver");
  else if (problem == (HRESULT)DIERR_EFFECTPLAYING)
    sprintf(buffer, "Effect already playing");
  else if (problem == (HRESULT)DIERR_INCOMPLETEEFFECT)
    sprintf(buffer, "Incomplete effect");
  else if (problem == (HRESULT)DIERR_MOREDATA)
    sprintf(buffer, "Return buffer not large enough");
  else if (problem == (HRESULT)DIERR_NOTACQUIRED)
    sprintf(buffer, "Device not acquired");
  else if (problem == (HRESULT)DIERR_NOTDOWNLOADED)
    sprintf(buffer, "Effect not downloaded");
  else if (problem == (HRESULT)DIERR_NOTINITIALIZED)
    sprintf(buffer, "Device not initialized");
  else if (problem == (HRESULT)DIERR_OUTOFMEMORY)
    sprintf(buffer, "Out of memory");
  else if (problem == (HRESULT)DIERR_UNSUPPORTED)
    sprintf(buffer, "Action not supported by driver");
  else
    sprintf(buffer, "Unknown error (%d)", (int)problem);
  printError(TextUtils::format("%s (%s).", situation, buffer));
}

/* Nasty callbacks 'cause DirectX sucks */

BOOL CALLBACK DXJoystick::deviceEnumCallback(LPCDIDEVICEINSTANCE device, void* /*pvRef*/)
{
  if (!device)
    return DIENUM_STOP;

  devices.push_back(*device);

  return DIENUM_CONTINUE;
}

#endif

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<|endoftext|>


<commit_before>/* Copyright (c) 2013-2016 Jeffrey Pfau
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ObjView.h"

#include "GBAApp.h"

#include <QFontDatabase>
#include <QTimer>

extern "C" {
#include "gba/gba.h"
#ifdef M_CORE_GB
#include "gb/gb.h"
#endif
}

using namespace QGBA;

ObjView::ObjView(GameController* controller, QWidget* parent)
	: AssetView(controller, parent)
	, m_controller(controller)
	, m_tileStatus{}
	, m_objId(0)
{
	m_ui.setupUi(this);
	m_ui.tile->setController(controller);

	const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);

	m_ui.x->setFont(font);
	m_ui.y->setFont(font);
	m_ui.w->setFont(font);
	m_ui.h->setFont(font);
	m_ui.address->setFont(font);
	m_ui.priority->setFont(font);
	m_ui.palette->setFont(font);
	m_ui.transform->setFont(font);
	m_ui.mode->setFont(font);

	connect(m_ui.tiles, SIGNAL(indexPressed(int)), this, SLOT(translateIndex(int)));
	connect(m_ui.objId, SIGNAL(valueChanged(int)), this, SLOT(selectObj(int)));
	connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
		updateTiles(true);
	});
}

void ObjView::selectObj(int obj) {
	m_objId = obj;
	updateTiles(true);
}

void ObjView::translateIndex(int index) {
	m_ui.tile->selectIndex(index + m_tileOffset);
}

#ifdef M_CORE_GBA
void ObjView::updateTilesGBA(bool force) {
	const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
	const GBAObj* obj = &gba->video.oam.obj[m_objId];

	unsigned shape = GBAObjAttributesAGetShape(obj->a);
	unsigned size = GBAObjAttributesBGetSize(obj->b);
	unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
	unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
	m_ui.tiles->setTileCount(width * height / 64);
	m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
	unsigned palette = GBAObjAttributesCGetPalette(obj->c);
	unsigned tile = GBAObjAttributesCGetTile(obj->c);
	int i = 0;
	// TODO: Tile stride
	// TODO: Check to see if parameters are changed (so as to enable force if needed)
	if (GBAObjAttributesAIs256Color(obj->a)) {
		m_ui.palette->setText("256-color");
		mTileCacheSetPalette(m_tileCache.get(), 1);
		m_ui.tile->setPalette(0);
		m_ui.tile->setPaletteSet(1, 1024, 1536);
		tile /= 2;
		for (int y = 0; y < height / 8; ++y) {
			for (int x = 0; x < width / 8; ++x, ++i) {
				unsigned t = tile + i;
				const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * t], t + 1024, 1);
				if (data) {
					m_ui.tiles->setTile(i, data);
				} else if (force) {
					m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t + 1024, 1));
				}
			}
		}
		tile += 1024;
	} else {
		m_ui.palette->setText(QString::number(palette));
		mTileCacheSetPalette(m_tileCache.get(), 0);
		m_ui.tile->setPalette(palette);
		m_ui.tile->setPaletteSet(0, 2048, 3072);
		for (int y = 0; y < height / 8; ++y) {
			for (int x = 0; x < width / 8; ++x, ++i) {
				unsigned t = tile + i;
				const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * t], t + 2048, palette + 16);
				if (data) {
					m_ui.tiles->setTile(i, data);
				} else if (force) {
					m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t + 2048, palette + 16));
				}
			}
		}
		tile += 2048;
	}
	m_tileOffset = tile;

	m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b)));
	m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a)));
	m_ui.w->setText(QString::number(width));
	m_ui.h->setText(QString::number(height));

	m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
	m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c)));
	m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b));
	m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b));
	m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a));
	m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
	m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));

	if (GBAObjAttributesAIsTransformed(obj->a)) {
		m_ui.transform->setText(QString::number(GBAObjAttributesBGetMatIndex(obj->b)));
	} else {
		m_ui.transform->setText(tr("Off"));
	}

	switch (GBAObjAttributesAGetMode(obj->a)) {
	case OBJ_MODE_NORMAL:
		m_ui.mode->setText(tr("Normal"));
		break;
	case OBJ_MODE_SEMITRANSPARENT:
		m_ui.mode->setText(tr("Trans"));
		break;
	case OBJ_MODE_OBJWIN:
		m_ui.mode->setText(tr("OBJWIN"));
		break;
	default:
		m_ui.mode->setText(tr("Invalid"));
		break;
	}
}
#endif

#ifdef M_CORE_GB
void ObjView::updateTilesGB(bool force) {
	const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
	const GBObj* obj = &gb->video.oam.obj[m_objId];

	unsigned width = 8;
	unsigned height = 8; // TODO
	m_ui.tiles->setTileCount(width * height / 64);
	m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
	int palette = 0;
	unsigned tile = obj->tile;
	if (gb->model >= GB_MODEL_CGB) {
		if (GBObjAttributesIsBank(obj->attr)) {
			tile += 512;
		}
		palette = GBObjAttributesGetCGBPalette(obj->attr);
	} else {
		palette = GBObjAttributesGetPalette(obj->attr);
	}
	int i = 0;
	// TODO: Check to see if parameters are changed (so as to enable force if needed)
	m_ui.palette->setText(QString::number(palette));
	mTileCacheSetPalette(m_tileCache.get(), 0);
	m_ui.tile->setPalette(palette + 8);
	m_ui.tile->setPaletteSet(0, 512, 1024);
	for (int y = 0; y < height / 8; ++y, ++i) {
		unsigned t = tile + i;
		const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[16 * t], t, palette + 8);
		if (data) {
			m_ui.tiles->setTile(i, data);
		} else if (force) {
			m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t, palette + 8));
		}
	}
	m_tileOffset = tile;

	m_ui.x->setText(QString::number(obj->x));
	m_ui.y->setText(QString::number(obj->y));
	m_ui.w->setText(QString::number(width));
	m_ui.h->setText(QString::number(height));

	m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
	m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr)));
	m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr));
	m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr));
	m_ui.enabled->setChecked(obj->y == 0 || obj->y >= 160);
	m_ui.doubleSize->setChecked(false);
	m_ui.mosaic->setChecked(false);
	m_ui.transform->setText(tr("N/A"));
	m_ui.mode->setText(tr("N/A"));
}
#endif
<commit_msg>Qt: Fix GB sprite enable, add sprite heights for GB and mapping for GBA<commit_after>/* Copyright (c) 2013-2016 Jeffrey Pfau
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ObjView.h"

#include "GBAApp.h"

#include <QFontDatabase>
#include <QTimer>

extern "C" {
#include "gba/gba.h"
#ifdef M_CORE_GB
#include "gb/gb.h"
#include "gb/io.h"
#endif
}

using namespace QGBA;

ObjView::ObjView(GameController* controller, QWidget* parent)
	: AssetView(controller, parent)
	, m_controller(controller)
	, m_tileStatus{}
	, m_objId(0)
{
	m_ui.setupUi(this);
	m_ui.tile->setController(controller);

	const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);

	m_ui.x->setFont(font);
	m_ui.y->setFont(font);
	m_ui.w->setFont(font);
	m_ui.h->setFont(font);
	m_ui.address->setFont(font);
	m_ui.priority->setFont(font);
	m_ui.palette->setFont(font);
	m_ui.transform->setFont(font);
	m_ui.mode->setFont(font);

	connect(m_ui.tiles, SIGNAL(indexPressed(int)), this, SLOT(translateIndex(int)));
	connect(m_ui.objId, SIGNAL(valueChanged(int)), this, SLOT(selectObj(int)));
	connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
		updateTiles(true);
	});
}

void ObjView::selectObj(int obj) {
	m_objId = obj;
	updateTiles(true);
}

void ObjView::translateIndex(int index) {
	m_ui.tile->selectIndex(index + m_tileOffset);
}

#ifdef M_CORE_GBA
void ObjView::updateTilesGBA(bool force) {
	const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
	const GBAObj* obj = &gba->video.oam.obj[m_objId];

	unsigned shape = GBAObjAttributesAGetShape(obj->a);
	unsigned size = GBAObjAttributesBGetSize(obj->b);
	unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
	unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
	m_ui.tiles->setTileCount(width * height / 64);
	m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
	unsigned palette = GBAObjAttributesCGetPalette(obj->c);
	unsigned tile = GBAObjAttributesCGetTile(obj->c);
	GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
	int i = 0;
	// TODO: Tile stride
	// TODO: Check to see if parameters are changed (so as to enable force if needed)
	if (GBAObjAttributesAIs256Color(obj->a)) {
		m_ui.palette->setText("256-color");
		mTileCacheSetPalette(m_tileCache.get(), 1);
		m_ui.tile->setPalette(0);
		m_ui.tile->setPaletteSet(1, 1024, 1536);
		tile /= 2;
		for (int y = 0; y < height / 8; ++y) {
			for (int x = 0; x < width / 8; ++x, ++i) {
				unsigned t = tile + i;
				const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * t], t + 1024, 1);
				if (data) {
					m_ui.tiles->setTile(i, data);
				} else if (force) {
					m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t + 1024, 1));
				}
			}
		}
		tile += 1024;
	} else {
		m_ui.palette->setText(QString::number(palette));
		mTileCacheSetPalette(m_tileCache.get(), 0);
		m_ui.tile->setPalette(palette);
		m_ui.tile->setPaletteSet(0, 2048, 3072);
		unsigned t = tile + i;
		for (int y = 0; y < height / 8; ++y) {
			for (int x = 0; x < width / 8; ++x, ++i, ++t) {
				const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * t], t + 2048, palette + 16);
				if (data) {
					m_ui.tiles->setTile(i, data);
				} else if (force) {
					m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t + 2048, palette + 16));
				}
			}
			if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
				t += 0x20 - width / 8;
			}
		}
		tile += 2048;
	}
	m_tileOffset = tile;

	m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b)));
	m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a)));
	m_ui.w->setText(QString::number(width));
	m_ui.h->setText(QString::number(height));

	m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
	m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c)));
	m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b));
	m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b));
	m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a));
	m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
	m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));

	if (GBAObjAttributesAIsTransformed(obj->a)) {
		m_ui.transform->setText(QString::number(GBAObjAttributesBGetMatIndex(obj->b)));
	} else {
		m_ui.transform->setText(tr("Off"));
	}

	switch (GBAObjAttributesAGetMode(obj->a)) {
	case OBJ_MODE_NORMAL:
		m_ui.mode->setText(tr("Normal"));
		break;
	case OBJ_MODE_SEMITRANSPARENT:
		m_ui.mode->setText(tr("Trans"));
		break;
	case OBJ_MODE_OBJWIN:
		m_ui.mode->setText(tr("OBJWIN"));
		break;
	default:
		m_ui.mode->setText(tr("Invalid"));
		break;
	}
}
#endif

#ifdef M_CORE_GB
void ObjView::updateTilesGB(bool force) {
	const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
	const GBObj* obj = &gb->video.oam.obj[m_objId];

	unsigned width = 8;
	unsigned height = 8; // TODO
	GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
	if (GBRegisterLCDCIsObjSize(lcdc)) {
		height = 16;
	}
	m_ui.tiles->setTileCount(width * height / 64);
	m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
	int palette = 0;
	unsigned tile = obj->tile;
	if (gb->model >= GB_MODEL_CGB) {
		if (GBObjAttributesIsBank(obj->attr)) {
			tile += 512;
		}
		palette = GBObjAttributesGetCGBPalette(obj->attr);
	} else {
		palette = GBObjAttributesGetPalette(obj->attr);
	}
	int i = 0;
	// TODO: Check to see if parameters are changed (so as to enable force if needed)
	m_ui.palette->setText(QString::number(palette));
	mTileCacheSetPalette(m_tileCache.get(), 0);
	m_ui.tile->setPalette(palette + 8);
	m_ui.tile->setPaletteSet(0, 512, 1024);
	for (int y = 0; y < height / 8; ++y, ++i) {
		unsigned t = tile + i;
		const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[16 * t], t, palette + 8);
		if (data) {
			m_ui.tiles->setTile(i, data);
		} else if (force) {
			m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t, palette + 8));
		}
	}
	m_tileOffset = tile;

	m_ui.x->setText(QString::number(obj->x));
	m_ui.y->setText(QString::number(obj->y));
	m_ui.w->setText(QString::number(width));
	m_ui.h->setText(QString::number(height));

	m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
	m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr)));
	m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr));
	m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr));
	m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160);
	m_ui.doubleSize->setChecked(false);
	m_ui.mosaic->setChecked(false);
	m_ui.transform->setText(tr("N/A"));
	m_ui.mode->setText(tr("N/A"));
}
#endif
<|endoftext|>