<commit_before>/* bzflag
 * Copyright (c) 1993 - 2005 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 LICENSE 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.
 */

// Provide BZFS with a list server connection

/* class header */
#include "ListServerConnection.h"

/* system implementation headers */
#include <string.h>
#include <string>
#include <math.h>
#include <errno.h>

/* common implementation headers */
#include "bzfio.h"
#include "version.h"
#include "TextUtils.h"
#include "Protocol.h"
#include "GameKeeper.h"

/* local implementation headers */
#include "CmdLineOptions.h"

// FIXME remove externs!
extern Address serverAddress;
extern PingPacket getTeamCounts();
extern uint16_t curMaxPlayers;
extern int getTarget(const char *victimname);
extern void sendMessage(int playerIndex, PlayerId targetPlayer, const char *message);
extern void sendPlayerInfo(void);
extern void sendIPUpdate(int targetPlayer, int playerIndex);
extern CmdLineOptions *clOptions;

ListServerLink::ListServerLink(std::string listServerURL, std::string publicizedAddress, std::string publicizedTitle)
{
  // parse url
  std::string protocol, hostname, pathname;
  int port = 80;
  bool useDefault = false;

  // use default if it can't be parsed
  if (!BzfNetwork::parseURL(listServerURL, protocol, hostname, port, pathname))
    useDefault = true;

  // use default if wrong protocol or invalid port
  if ((protocol != "http") || (port < 1) || (port > 65535))
    useDefault = true;

  // use default if bad address
  Address address = Address::getHostAddress(hostname);
  if (address.isAny())
    useDefault = true;

  // parse default list server URL if we need to; assume default works
  if (useDefault) {
    BzfNetwork::parseURL(DefaultListServerURL, protocol, hostname, port, pathname);
    DEBUG1("Provided list server URL (%s) is invalid.  Using default of %s.\n", listServerURL.c_str(), DefaultListServerURL);
  }

  // add to list
  this->address	   = address;
  this->port	   = port;
  this->pathname   = pathname;
  this->hostname   = hostname;
  this->linkSocket = NotConnected;

  this->publicizeAddress     = publicizedAddress;
  this->publicizeDescription = publicizedTitle;
  this->publicizeServer	     = true;  //if this c'tor is called, it's safe to publicize

  // schedule initial ADD message
  queueMessage(ListServerLink::ADD);
}

ListServerLink::ListServerLink()
{
  // does not create a usable link, so checks should be placed
  // in  all public member functions to ensure that nothing tries
  // to happen if publicizeServer is false
  this->linkSocket = NotConnected;
  this->publicizeServer = false;
}

ListServerLink::~ListServerLink()
{
  // now tell the list server that we're going away.  this can
  // take some time but we don't want to wait too long.  we do
  // our own multiplexing loop and wait for a maximum of 3 seconds
  // total.

  // if we aren't supposed to be publicizing, skip the whole thing
  // and don't waste 3 seconds.
  if (!publicizeServer)
    return;

  queueMessage(ListServerLink::REMOVE);
  TimeKeeper start = TimeKeeper::getCurrent();
  do {
    // compute timeout
    float waitTime = 3.0f - (TimeKeeper::getCurrent() - start);
    if (waitTime <= 0.0f)
      break;
    if (!isConnected()) //queueMessage should have connected us
      break;
    // check for list server socket connection
    int fdMax = -1;
    fd_set write_set;
    fd_set read_set;
    FD_ZERO(&write_set);
    FD_ZERO(&read_set);
    if (phase == ListServerLink::CONNECTING)
      _FD_SET(linkSocket, &write_set);
    else
      _FD_SET(linkSocket, &read_set);
    fdMax = linkSocket;

    // wait for socket to connect or timeout
    struct timeval timeout;
    timeout.tv_sec = long(floorf(waitTime));
    timeout.tv_usec = long(1.0e+6f * (waitTime - floorf(waitTime)));
    int nfound = select(fdMax + 1, (fd_set*)&read_set, (fd_set*)&write_set,
			0, &timeout);
    if (nfound == 0)
      // Time has gone, close and go
      break;
    // check for connection to list server
    if (FD_ISSET(linkSocket, &write_set))
      sendQueuedMessages();
    else if (FD_ISSET(linkSocket, &read_set))
      read();
  } while (true);

  // stop list server communication
  closeLink();
}

void ListServerLink::closeLink()
{
  if (isConnected()) {
    close(linkSocket);
    DEBUG4("Closing List Server\n");
    linkSocket = NotConnected;
  }
}

void ListServerLink::read()
{
  if (isConnected()) {
    char buf[2048];
    int bytes = recv(linkSocket, buf, sizeof(buf)-1, 0);
    // TODO don't close unless we've got it all
    closeLink();
    buf[bytes]=0;
    char* base = buf;
    static char *tokGoodIdentifier = "TOKGOOD: ";
    static char *tokBadIdentifier = "TOKBAD: ";
    // walks entire reply including HTTP headers
    while (*base) {
      // find next newline
      char* scan = base;
      while (*scan && *scan != '\r' && *scan != '\n') scan++;
      // if no newline then no more complete replies
      if (*scan != '\r' && *scan != '\n') break;
      while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
      DEBUG4("Got line: \"%s\"\n", base);
      // TODO don't do this if we don't want central logins
      if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0) {
	char *callsign, *group;
	callsign = base + strlen(tokGoodIdentifier);
	DEBUG3("Got: %s\n", base);
	group = callsign;
	while (*group && (*group != ':')) group++;
	while (*group && (*group == ':')) *group++ = 0;
	int playerIndex = getTarget(callsign);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  // don't accept the global auth if there's a local account of the same name
	  // and the local account is not marked as being the same as the global account
	  if (!playerData->accessInfo.hasRealPassword()
	      || playerData->accessInfo.getUserInfo(callsign).hasGroup("LOCAL.GLOBAL")) {
	    if (!playerData->accessInfo.isRegistered())
	      playerData->accessInfo.storeInfo(NULL);
	    playerData->accessInfo.setPermissionRights();
	    while (*group) {
	      char *nextgroup = group;
	      while (*nextgroup && (*nextgroup != ':')) nextgroup++;
	      while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
	      playerData->accessInfo.addGroup(group);
	      //DEBUG3("Got: [%d] \"%s\" \"%s\"\n", playerIndex, callsign, group);
	      group = nextgroup;
	    }
	    sendMessage(ServerPlayer, playerIndex, "Global login approved!");
	    sendIPUpdate(playerIndex, -1);
	    sendPlayerInfo();
	  } else {
	    sendMessage(ServerPlayer, playerIndex, "Global login rejected. "
			"This callsign is registered locally on this server.");
	    sendMessage(ServerPlayer, playerIndex, "If the local account is yours, "
			"please ask an admin to grant it membership in the LOCAL.GLOBAL group.");
	    sendMessage(ServerPlayer, playerIndex, "If it is not yours, please ask an admin "
			"to deregister it so that you may use your global callsign.");
	  }
	  playerData->player.clearToken();
	}
      } else if (strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier)) == 0) {
	char *callsign;
	callsign = base + strlen(tokBadIdentifier);
	int playerIndex = getTarget(callsign);
	DEBUG3("Got: [%d] %s\n", playerIndex, base);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  sendMessage(ServerPlayer, playerIndex, "Global login rejected, bad token.");
	  playerData->player.clearToken();
	}
      }

      // next reply
      base = scan;
    }
    if (nextMessageType != ListServerLink::NONE) {
      // There was a pending request arrived after we write:
      // we should redo all the stuff
      openLink();
    }
  }
}

void ListServerLink::openLink()
{
  // start opening connection if not already doing so
  if (!isConnected()) {
    linkSocket = socket(AF_INET, SOCK_STREAM, 0);
    DEBUG4("Opening List Server\n");
    if (!isConnected()) {
      return;
    }

    // set to non-blocking for connect
    if (BzfNetwork::setNonBlocking(linkSocket) < 0) {
      closeLink();
      return;
    }

    // Make our connection come from our serverAddress in case we have
    // multiple/masked IPs so the list server can verify us.
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr = serverAddress;

    // assign the address to the socket
    if (bind(linkSocket, (CNCTType*)&addr, sizeof(addr)) < 0) {
      closeLink();
      return;
    }

    // connect.  this should fail with EINPROGRESS but check for
    // success just in case.
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(port);
    addr.sin_addr   = address;
    if (connect(linkSocket, (CNCTType*)&addr, sizeof(addr)) < 0) {
#if defined(_WIN32)
#undef EINPROGRESS
#define EINPROGRESS EWOULDBLOCK
#endif
      if (getErrno() != EINPROGRESS) {
	nerror("connecting to list server");
	// try to lookup dns name again in case it moved
	this->address = Address::getHostAddress(this->hostname);
	closeLink();
      } else {
	phase = CONNECTING;
      }
    } else {
      // shouldn't arrive here. Just in case, clean
      DEBUG3("list server connect and close?\n");
      closeLink();
    }
  }
}

void ListServerLink::queueMessage(MessageType type)
{
  // ignore if the server is not public
  if (!publicizeServer) return;

  // Open network connection only if closed
  if (!isConnected()) openLink();

  // record next message to send.
  nextMessageType = type;
}

void ListServerLink::sendQueuedMessages()
{
  if (!isConnected())
    return;

  if (nextMessageType == ListServerLink::ADD) {
    DEBUG3("Queuing ADD message to list server\n");
    addMe(getTeamCounts(), publicizeAddress, TextUtils::url_encode(publicizeDescription));
    lastAddTime = TimeKeeper::getCurrent();
  } else if (nextMessageType == ListServerLink::REMOVE) {
    DEBUG3("Queuing REMOVE message to list server\n");
    removeMe(publicizeAddress);
  }
}

void ListServerLink::addMe(PingPacket pingInfo,
			   std::string publicizedAddress,
			   std::string publicizedTitle)
{
  std::string msg;

  // encode ping reply as ascii hex digits plus NULL
  char gameInfo[PingPacketHexPackedSize + 1];
  pingInfo.packHex(gameInfo);

  // TODO we probably should convert to a POST. List server now allows either
  // send ADD message (must send blank line)
  msg = TextUtils::format("GET %s?action=ADD&nameport=%s&version=%s&gameinfo=%s&build=%s",
    pathname.c_str(), publicizedAddress.c_str(),
    getServerVersion(), gameInfo,
    getAppVersion());
  msg += "&checktokens=";
  // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    NetHandler *handler = playerData->netHandler;
    std::string encodedCallsign = TextUtils::url_encode(playerData->player.getCallSign());
    if (strlen(playerData->player.getCallSign()) && strlen(playerData->player.getToken())) {
      msg += encodedCallsign;
      Address addr = handler->getIPAddress();
      if (!addr.isPrivate()) {
        msg += "@";
        msg += handler->getTargetIP();
      }
      msg += "=";
      msg += playerData->player.getToken();
      msg += "%0D%0A";
    }
  }
  // *groups=GROUP0%0D%0AGROUP1%0D%0A
  msg += "&groups=";
  PlayerAccessMap::iterator itr = groupAccess.begin();
  for ( ; itr != groupAccess.end(); itr++) {
    if (itr->first.substr(0, 6) != "LOCAL.") {
      msg += itr->first.c_str();
      msg += "%0D%0A";
    }
  }

  msg += TextUtils::format("&title=%s HTTP/1.1\r\n"
      "User-Agent: bzfs %s\r\n"
      "Host: %s\r\n"
      "Cache-Control: no-cache\r\n"
      "Connection: close\r\n"
      "\r\n",
    publicizedTitle.c_str(),
    getAppVersion(),
    hostname.c_str());
  sendLSMessage(msg);
}

void ListServerLink::removeMe(std::string publicizedAddress)
{
  std::string msg;
  // send REMOVE (must send blank line)
  msg = TextUtils::format("GET %s?action=REMOVE&nameport=%s HTTP/1.1\r\n"
    "User-Agent: bzfs %s\r\n"
    "Host: %s\r\n"
    "Cache-Control: no-cache\r\n"
    "Connection: close\r\n"
    "\r\n",
    pathname.c_str(),
    publicizedAddress.c_str(),
    getAppVersion(),
    hostname.c_str());
  sendLSMessage(msg);
}

void ListServerLink::sendLSMessage(std::string message)
{
  const int bufsize = 4096;
  char msg[bufsize];
  strncpy(msg, message.c_str(), bufsize);
  msg[bufsize - 1] = 0;
  if (strlen(msg) > 0) {
    DEBUG3("%s\n", msg);
    if (send(linkSocket, msg, strlen(msg), 0) == -1) {
      perror("List server send failed");
      DEBUG3("Unable to send to the list server!\n");
      closeLink();
    } else {
      nextMessageType = ListServerLink::NONE;
      phase	   = ListServerLink::WRITTEN;
    }
  } else {
    closeLink();
  }
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<commit_msg>ws, other options<commit_after>/* bzflag
 * Copyright (c) 1993 - 2005 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 LICENSE 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.
 */

// Provide BZFS with a list server connection

/* class header */
#include "ListServerConnection.h"

/* system implementation headers */
#include <string.h>
#include <string>
#include <math.h>
#include <errno.h>

/* common implementation headers */
#include "bzfio.h"
#include "version.h"
#include "TextUtils.h"
#include "Protocol.h"
#include "GameKeeper.h"

/* local implementation headers */
#include "CmdLineOptions.h"

// FIXME remove externs!
extern Address serverAddress;
extern PingPacket getTeamCounts();
extern uint16_t curMaxPlayers;
extern int getTarget(const char *victimname);
extern void sendMessage(int playerIndex, PlayerId targetPlayer, const char *message);
extern void sendPlayerInfo(void);
extern void sendIPUpdate(int targetPlayer, int playerIndex);
extern CmdLineOptions *clOptions;

ListServerLink::ListServerLink(std::string listServerURL, std::string publicizedAddress, std::string publicizedTitle)
{
  // parse url
  std::string protocol, hostname, pathname;
  int port = 80;
  bool useDefault = false;

  // use default if it can't be parsed
  if (!BzfNetwork::parseURL(listServerURL, protocol, hostname, port, pathname))
    useDefault = true;

  // use default if wrong protocol or invalid port
  if ((protocol != "http") || (port < 1) || (port > 65535))
    useDefault = true;

  // use default if bad address
  Address address = Address::getHostAddress(hostname);
  if (address.isAny())
    useDefault = true;

  // parse default list server URL if we need to; assume default works
  if (useDefault) {
    BzfNetwork::parseURL(DefaultListServerURL, protocol, hostname, port, pathname);
    DEBUG1("Provided list server URL (%s) is invalid.  Using default of %s.\n", listServerURL.c_str(), DefaultListServerURL);
  }

  // add to list
  this->address	   = address;
  this->port	   = port;
  this->pathname   = pathname;
  this->hostname   = hostname;
  this->linkSocket = NotConnected;

  this->publicizeAddress     = publicizedAddress;
  this->publicizeDescription = publicizedTitle;
  this->publicizeServer	     = true;  //if this c'tor is called, it's safe to publicize

  // schedule initial ADD message
  queueMessage(ListServerLink::ADD);
}

ListServerLink::ListServerLink()
{
  // does not create a usable link, so checks should be placed
  // in  all public member functions to ensure that nothing tries
  // to happen if publicizeServer is false
  this->linkSocket = NotConnected;
  this->publicizeServer = false;
}

ListServerLink::~ListServerLink()
{
  // now tell the list server that we're going away.  this can
  // take some time but we don't want to wait too long.  we do
  // our own multiplexing loop and wait for a maximum of 3 seconds
  // total.

  // if we aren't supposed to be publicizing, skip the whole thing
  // and don't waste 3 seconds.
  if (!publicizeServer)
    return;

  queueMessage(ListServerLink::REMOVE);
  TimeKeeper start = TimeKeeper::getCurrent();
  do {
    // compute timeout
    float waitTime = 3.0f - (TimeKeeper::getCurrent() - start);
    if (waitTime <= 0.0f)
      break;
    if (!isConnected()) //queueMessage should have connected us
      break;
    // check for list server socket connection
    int fdMax = -1;
    fd_set write_set;
    fd_set read_set;
    FD_ZERO(&write_set);
    FD_ZERO(&read_set);
    if (phase == ListServerLink::CONNECTING)
      _FD_SET(linkSocket, &write_set);
    else
      _FD_SET(linkSocket, &read_set);
    fdMax = linkSocket;

    // wait for socket to connect or timeout
    struct timeval timeout;
    timeout.tv_sec = long(floorf(waitTime));
    timeout.tv_usec = long(1.0e+6f * (waitTime - floorf(waitTime)));
    int nfound = select(fdMax + 1, (fd_set*)&read_set, (fd_set*)&write_set,
			0, &timeout);
    if (nfound == 0)
      // Time has gone, close and go
      break;
    // check for connection to list server
    if (FD_ISSET(linkSocket, &write_set))
      sendQueuedMessages();
    else if (FD_ISSET(linkSocket, &read_set))
      read();
  } while (true);

  // stop list server communication
  closeLink();
}

void ListServerLink::closeLink()
{
  if (isConnected()) {
    close(linkSocket);
    DEBUG4("Closing List Server\n");
    linkSocket = NotConnected;
  }
}

void ListServerLink::read()
{
  if (isConnected()) {
    char buf[2048];
    int bytes = recv(linkSocket, buf, sizeof(buf)-1, 0);
    // TODO don't close unless we've got it all
    closeLink();
    buf[bytes]=0;
    char* base = buf;
    static char *tokGoodIdentifier = "TOKGOOD: ";
    static char *tokBadIdentifier = "TOKBAD: ";
    // walks entire reply including HTTP headers
    while (*base) {
      // find next newline
      char* scan = base;
      while (*scan && *scan != '\r' && *scan != '\n') scan++;
      // if no newline then no more complete replies
      if (*scan != '\r' && *scan != '\n') break;
      while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
      DEBUG4("Got line: \"%s\"\n", base);
      // TODO don't do this if we don't want central logins
      if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0) {
	char *callsign, *group;
	callsign = base + strlen(tokGoodIdentifier);
	DEBUG3("Got: %s\n", base);
	group = callsign;
	while (*group && (*group != ':')) group++;
	while (*group && (*group == ':')) *group++ = 0;
	int playerIndex = getTarget(callsign);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  // don't accept the global auth if there's a local account of the same name
	  // and the local account is not marked as being the same as the global account
	  if (!playerData->accessInfo.hasRealPassword()
	      || playerData->accessInfo.getUserInfo(callsign).hasGroup("LOCAL.GLOBAL")) {
	    if (!playerData->accessInfo.isRegistered())
	      playerData->accessInfo.storeInfo(NULL);
	    playerData->accessInfo.setPermissionRights();
	    while (*group) {
	      char *nextgroup = group;
	      while (*nextgroup && (*nextgroup != ':')) nextgroup++;
	      while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
	      playerData->accessInfo.addGroup(group);
	      //DEBUG3("Got: [%d] \"%s\" \"%s\"\n", playerIndex, callsign, group);
	      group = nextgroup;
	    }
	    sendMessage(ServerPlayer, playerIndex, "Global login approved!");
	    sendIPUpdate(playerIndex, -1);
	    sendPlayerInfo();
	  } else {
	    sendMessage(ServerPlayer, playerIndex, "Global login rejected. "
			"This callsign is registered locally on this server.");
	    sendMessage(ServerPlayer, playerIndex, "If the local account is yours, "
			"either /identify then /deregister and reconnnect, "
			"or ask an admin to add you to the  LOCAL.GLOBAL group.");
	    sendMessage(ServerPlayer, playerIndex, "If it is not yours, please ask an admin "
			"to deregister it so that you may use your global callsign.");
	  }
	  playerData->player.clearToken();
	}
      } else if (strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier)) == 0) {
	char *callsign;
	callsign = base + strlen(tokBadIdentifier);
	int playerIndex = getTarget(callsign);
	DEBUG3("Got: [%d] %s\n", playerIndex, base);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  sendMessage(ServerPlayer, playerIndex, "Global login rejected, bad token.");
	  playerData->player.clearToken();
	}
      }

      // next reply
      base = scan;
    }
    if (nextMessageType != ListServerLink::NONE) {
      // There was a pending request arrived after we write:
      // we should redo all the stuff
      openLink();
    }
  }
}

void ListServerLink::openLink()
{
  // start opening connection if not already doing so
  if (!isConnected()) {
    linkSocket = socket(AF_INET, SOCK_STREAM, 0);
    DEBUG4("Opening List Server\n");
    if (!isConnected()) {
      return;
    }

    // set to non-blocking for connect
    if (BzfNetwork::setNonBlocking(linkSocket) < 0) {
      closeLink();
      return;
    }

    // Make our connection come from our serverAddress in case we have
    // multiple/masked IPs so the list server can verify us.
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr = serverAddress;

    // assign the address to the socket
    if (bind(linkSocket, (CNCTType*)&addr, sizeof(addr)) < 0) {
      closeLink();
      return;
    }

    // connect.  this should fail with EINPROGRESS but check for
    // success just in case.
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(port);
    addr.sin_addr   = address;
    if (connect(linkSocket, (CNCTType*)&addr, sizeof(addr)) < 0) {
#if defined(_WIN32)
#undef EINPROGRESS
#define EINPROGRESS EWOULDBLOCK
#endif
      if (getErrno() != EINPROGRESS) {
	nerror("connecting to list server");
	// try to lookup dns name again in case it moved
	this->address = Address::getHostAddress(this->hostname);
	closeLink();
      } else {
	phase = CONNECTING;
      }
    } else {
      // shouldn't arrive here. Just in case, clean
      DEBUG3("list server connect and close?\n");
      closeLink();
    }
  }
}

void ListServerLink::queueMessage(MessageType type)
{
  // ignore if the server is not public
  if (!publicizeServer) return;

  // Open network connection only if closed
  if (!isConnected()) openLink();

  // record next message to send.
  nextMessageType = type;
}

void ListServerLink::sendQueuedMessages()
{
  if (!isConnected())
    return;

  if (nextMessageType == ListServerLink::ADD) {
    DEBUG3("Queuing ADD message to list server\n");
    addMe(getTeamCounts(), publicizeAddress, TextUtils::url_encode(publicizeDescription));
    lastAddTime = TimeKeeper::getCurrent();
  } else if (nextMessageType == ListServerLink::REMOVE) {
    DEBUG3("Queuing REMOVE message to list server\n");
    removeMe(publicizeAddress);
  }
}

void ListServerLink::addMe(PingPacket pingInfo,
			   std::string publicizedAddress,
			   std::string publicizedTitle)
{
  std::string msg;

  // encode ping reply as ascii hex digits plus NULL
  char gameInfo[PingPacketHexPackedSize + 1];
  pingInfo.packHex(gameInfo);

  // TODO we probably should convert to a POST. List server now allows either
  // send ADD message (must send blank line)
  msg = TextUtils::format("GET %s?action=ADD&nameport=%s&version=%s&gameinfo=%s&build=%s",
    pathname.c_str(), publicizedAddress.c_str(),
    getServerVersion(), gameInfo,
    getAppVersion());
  msg += "&checktokens=";
  // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    NetHandler *handler = playerData->netHandler;
    std::string encodedCallsign = TextUtils::url_encode(playerData->player.getCallSign());
    if (strlen(playerData->player.getCallSign()) && strlen(playerData->player.getToken())) {
      msg += encodedCallsign;
      Address addr = handler->getIPAddress();
      if (!addr.isPrivate()) {
        msg += "@";
        msg += handler->getTargetIP();
      }
      msg += "=";
      msg += playerData->player.getToken();
      msg += "%0D%0A";
    }
  }
  // *groups=GROUP0%0D%0AGROUP1%0D%0A
  msg += "&groups=";
  PlayerAccessMap::iterator itr = groupAccess.begin();
  for ( ; itr != groupAccess.end(); itr++) {
    if (itr->first.substr(0, 6) != "LOCAL.") {
      msg += itr->first.c_str();
      msg += "%0D%0A";
    }
  }

  msg += TextUtils::format("&title=%s HTTP/1.1\r\n"
      "User-Agent: bzfs %s\r\n"
      "Host: %s\r\n"
      "Cache-Control: no-cache\r\n"
      "Connection: close\r\n"
      "\r\n",
    publicizedTitle.c_str(),
    getAppVersion(),
    hostname.c_str());
  sendLSMessage(msg);
}

void ListServerLink::removeMe(std::string publicizedAddress)
{
  std::string msg;
  // send REMOVE (must send blank line)
  msg = TextUtils::format("GET %s?action=REMOVE&nameport=%s HTTP/1.1\r\n"
    "User-Agent: bzfs %s\r\n"
    "Host: %s\r\n"
    "Cache-Control: no-cache\r\n"
    "Connection: close\r\n"
    "\r\n",
    pathname.c_str(),
    publicizedAddress.c_str(),
    getAppVersion(),
    hostname.c_str());
  sendLSMessage(msg);
}

void ListServerLink::sendLSMessage(std::string message)
{
  const int bufsize = 4096;
  char msg[bufsize];
  strncpy(msg, message.c_str(), bufsize);
  msg[bufsize - 1] = 0;
  if (strlen(msg) > 0) {
    DEBUG3("%s\n", msg);
    if (send(linkSocket, msg, strlen(msg), 0) == -1) {
      perror("List server send failed");
      DEBUG3("Unable to send to the list server!\n");
      closeLink();
    } else {
      nextMessageType = ListServerLink::NONE;
      phase	   = ListServerLink::WRITTEN;
    }
  } else {
    closeLink();
  }
}

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


<commit_before>/*
 * This file is open source software, licensed to you under the terms
 * of the Apache License, Version 2.0 (the "License").  See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.  You may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/*
 * Copyright (C) 2015 Cloudius Systems, Ltd.
 */

#pragma once

// File <-> streams adapters
//
// Seastar files are block-based due to the reliance on DMA - you must read
// on sector boundaries.  The adapters in this file provide a byte stream
// interface to files, while retaining the zero-copy characteristics of
// seastar files.

#include "file.hh"
#include "iostream.hh"
#include "shared_ptr.hh"

/// Data structure describing options for opening a file input stream
struct file_input_stream_options {
    uint64_t offset = 0;          ///< File offset at which to start reading
    size_t buffer_size = 8192;    ///< I/O buffer size
    unsigned read_ahead = 3;      ///< Number of extra read-ahead operations
};

// Create an input_stream for a given file, with the specified options.
// Multiple fibers of execution (continuations) may safely open
// multiple input streams concurrently for the same file.
input_stream<char> make_file_input_stream(
        file file,
        file_input_stream_options options = {});

// Create an input_stream for reading starting at a given position of the
// given file. Multiple fibers of execution (continuations) may safely open
// multiple input streams concurrently for the same file.
input_stream<char> make_file_input_stream(
        file file, uint64_t offset,
        uint64_t buffer_size = file_input_stream_options().buffer_size);

struct file_output_stream_options {
    unsigned buffer_size = 8192;
    unsigned preallocation_size = 1024*1024; // 1MB
    unsigned write_behind = 3; ///< Number of buffers to write in parallel
};

// Create an output_stream for writing starting at the position zero of a
// newly created file.
// NOTE: flush() should be the last thing to be called on a file output stream.
output_stream<char> make_file_output_stream(
        file file,
        uint64_t buffer_size = 8192);

/// Create an output_stream for writing starting at the position zero of a
/// newly created file.
/// NOTE: flush() should be the last thing to be called on a file output stream.
output_stream<char> make_file_output_stream(
        file file,
        file_output_stream_options options);

<commit_msg>fstream: disable read-ahead for now<commit_after>/*
 * This file is open source software, licensed to you under the terms
 * of the Apache License, Version 2.0 (the "License").  See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.  You may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/*
 * Copyright (C) 2015 Cloudius Systems, Ltd.
 */

#pragma once

// File <-> streams adapters
//
// Seastar files are block-based due to the reliance on DMA - you must read
// on sector boundaries.  The adapters in this file provide a byte stream
// interface to files, while retaining the zero-copy characteristics of
// seastar files.

#include "file.hh"
#include "iostream.hh"
#include "shared_ptr.hh"

/// Data structure describing options for opening a file input stream
struct file_input_stream_options {
    uint64_t offset = 0;          ///< File offset at which to start reading
    size_t buffer_size = 8192;    ///< I/O buffer size
    unsigned read_ahead = 0;      ///< Number of extra read-ahead operations
};

// Create an input_stream for a given file, with the specified options.
// Multiple fibers of execution (continuations) may safely open
// multiple input streams concurrently for the same file.
input_stream<char> make_file_input_stream(
        file file,
        file_input_stream_options options = {});

// Create an input_stream for reading starting at a given position of the
// given file. Multiple fibers of execution (continuations) may safely open
// multiple input streams concurrently for the same file.
input_stream<char> make_file_input_stream(
        file file, uint64_t offset,
        uint64_t buffer_size = file_input_stream_options().buffer_size);

struct file_output_stream_options {
    unsigned buffer_size = 8192;
    unsigned preallocation_size = 1024*1024; // 1MB
    unsigned write_behind = 3; ///< Number of buffers to write in parallel
};

// Create an output_stream for writing starting at the position zero of a
// newly created file.
// NOTE: flush() should be the last thing to be called on a file output stream.
output_stream<char> make_file_output_stream(
        file file,
        uint64_t buffer_size = 8192);

/// Create an output_stream for writing starting at the position zero of a
/// newly created file.
/// NOTE: flush() should be the last thing to be called on a file output stream.
output_stream<char> make_file_output_stream(
        file file,
        file_output_stream_options options);

<|endoftext|>


<commit_before>#ifndef VEXCL_FFT_KERNELS_HPP
#define VEXCL_FFT_KERNELS_HPP

/*
The MIT License

Copyright (c) 2012-2013 Denis Demidov <ddemidov@ksu.ru>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * \file   fft/kernels.hpp
 * \author Pascal Germroth <pascal@ensieve.org>
 * \brief  Kernel generator for FFT.
 */

// OpenCL FFT kernels, original by Eric Bainville

namespace vex {
namespace fft {

// Store v=b^e as components.
struct pow {
    size_t base, exponent, value;
    pow(size_t b, size_t e) : base(b), exponent(e), value(std::pow(b, e)) {}
};

inline std::ostream &operator<<(std::ostream &o, const pow &p) {
    return o << p.base << '^' << p.exponent << '=' << p.value;
}

/// Reverse the digits of a number in the base given.
/** 0 <= i < m,
 * i is converted into a base m.base-number,
 * its digits reversed, and converted back into an integer. */
size_t digit_reverse(size_t i, pow m) {
    assert(i < m.value);
    // convert to other base, digits[0]=least significant
    // convert back with digits[0]=most significant.
    size_t j = 0;
    for(size_t k = 0 ; k < m.exponent ; k++) {
        j = j * m.base + (i % m.base);
        i /= m.base;
    }
    return j;
}

/// a is a power of 2, b is an arbitrary number.
/** returns a power of 2 <= a that is <= b. */
size_t min_pow2(size_t a, size_t b) {
    while(a > b)
        a >>= 1;
    return a;
}

/// ceil(x/m) * m
size_t int_ceil(size_t x, size_t m) {
    return (x + m - 1) / m * m;
}

struct kernel_call {
    std::string desc;
    cl::Program program;
    cl::Kernel kernel;
    cl::NDRange global, local;
    kernel_call(std::string d, cl::Program p, cl::Kernel k, cl::NDRange g, cl::NDRange l) : desc(d), program(p), kernel(k), global(g), local(l) {}
};

// generates "(prefix vfrom,vfrom+1,...,vto)"
inline void param_list(std::ostringstream &o, std::string prefix, size_t from, size_t to, size_t step = 1) {
    o << '(';
    for(size_t i = from ; i != to ; i += step) {
        if(i != from) o << ", ";
        o << prefix << 'v' << i;
    } o << ')';
}

// omega(n,k) = exp(-i 2 pi k / n)
template<class T>
inline typename cl_vector_of<T,2>::type omega(int k, int n) {
    const T alpha = -2 * M_PI * (k % n) / n;
    return {{std::cos(alpha), std::sin(alpha)}};
}

template <class T>
void in_place_dft(std::ostringstream &o, pow radix) {
    typedef typename cl_vector_of<T,2>::type T2;
    o << "void dft" << radix.value;
    param_list(o, "real2_t *", 0, radix.value);
    o << "{\n";

    if(radix.exponent == 1) { // prime bases
        if(radix.value == 2) {
            o << "  real2_t tmp = *v0 - *v1;\n"
              << "  *v0 += *v1;\n"
              << "  *v1 = tmp;\n";
        } else {
            // naive DFT
            for(size_t i = 1 ; i < radix.base ; i++)
                o << "  real2_t w" << i << " = " << omega<T>(i, radix.base) << ";\n";
            for(size_t i = 0 ; i < radix.base ; i++) {
                o << "  real2_t t" << i << " = *v0";
                for(size_t j = 1 ; j < radix.base ; j++) {
                    o << " + ";
                    if(i == 0)
                        o << "*v" << j;
                    else
                        o << "mul(*v" << j << ", w" << ((i * j) % radix.base) << ")";
                }
                o << ";\n";
            }
            // write back
            for(size_t i = 0 ; i < radix.base ; i++)
                o << "  *v" << i << " = t" << i << ";\n";
        }
    } else { // recursive prime power DFTs
        const size_t prev_radix = radix.value / radix.base;
        // leaves
        for(size_t i = 0 ; i < prev_radix ; i++) {
            o << "  dft" << radix.base;
            param_list(o, "", i, i + radix.value, prev_radix);
            o << ";\n";
        }
        // twiddle
        for(size_t i = 1 ; i <= (prev_radix-1) * (radix.base-1) ; i++)
            o << "  real2_t w" << i << " = " << omega<T>(i, radix.value) << ";\n";
        for(size_t i = 1 ; i < prev_radix ; i++) {
            for(size_t j = 1 ; j < radix.base ; j++) {
                const size_t k = i + j * prev_radix;
                o << "  *v" << k << " = mul(*v" << k << ", w" << ((i * j) % radix.value) << ");\n";
            }
        }
        // next stage
        for(size_t i = 0 ; i < radix.base ; i++) {
            o << "  dft" << prev_radix;
            param_list(o, "", i * prev_radix, (i + 1) * prev_radix);
            o << ";\n";
        }
    }
    o << "}\n";
}

template <class T>
void kernel_radix(std::ostringstream &o, pow radix) {
    for(size_t e = 1 ; e <= radix.exponent ; e++)
        in_place_dft<T>(o, pow(radix.base, e));
    // kernel.
    o << "__kernel void radix(__global const real2_t *x, __global real2_t *y, uint p) {\n"
      << "  const size_t i = get_global_id(0);\n"
      << "  const size_t threads = get_global_size(0);\n"
        // index in input sequence, in 0..P-1
      << "  const size_t k = i % p;\n"
      << "  const size_t batch_offset = get_global_id(1) * threads * " << radix.value << ";\n";

    // read
    o << "  x += i + batch_offset;\n";
    for(size_t i = 0 ; i < radix.value ; i++)
        o << "  real2_t v" << i << " = x[" << i << " * threads];\n";

    // twiddle
    o << "  if(p != 1) {\n";
    for(size_t i = 1 ; i < radix.value ; i++) {
        const T alpha = -2 * M_PI * i / radix.value;
        o << "    v" << i << " = twiddle(v" << i << ", "
          << "(real_t)" << alpha << " * k / p);\n";
    }
    o << "  }\n";

    // inplace DFT
    o << "  dft" << radix.value;
    param_list(o, "&", 0, radix.value);
    o << ";\n";

    // write back
    o << "  const size_t j = k + (i - k) * " << radix.value << ";\n";
    o << "  y += j + batch_offset;\n";
    for(size_t i = 0 ; i < radix.value ; i++)
        o << "  y[" << i << " * p] = v" << digit_reverse(i, radix) << ";\n";
    o << "}\n";
}

template <class T>
void kernel_common(std::ostringstream &o) {
    if(std::is_same<T, cl_double>::value) {
        o << standard_kernel_header
          << "typedef double real_t;\n"
          << "typedef double2 real2_t;\n";
    } else {
        o << "typedef float real_t;\n"
          << "typedef float2 real2_t;\n";
    }
}

template <class T>
kernel_call radix_kernel(cl::CommandQueue &queue, size_t n, size_t batch, bool invert, pow radix, size_t p, cl::Buffer in, cl::Buffer out) {
    std::ostringstream o;
    o << std::setprecision(25);
    kernel_common<T>(o);

    // Return A*B (complex multiplication)
    o << "real2_t mul(real2_t a, real2_t b) {\n";
    if(invert) // conjugate b
        o << "  return (real2_t)(a.x * b.x + a.y * b.y, a.y * b.x - a.x * b.y);\n";
    else
        o << "  return (real2_t)(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);\n";
    o << "}\n";

    // A * exp(alpha * I) == A  * (cos(alpha) + I * sin(alpha))
    // native_cos(), native_sin() is a *lot* faster than sincos, on nVidia.
    o << "real2_t twiddle(real2_t a, real_t alpha) {\n";
    o << "  real_t cs, sn;\n";
    if(std::is_same<T, cl_double>::value)
        // use sincos with double since we probably want higher precision
        o << "  sn = sincos(alpha, &cs);\n";
    else
        // use native with float since we probably want higher performance
        o << "  cs = native_cos(alpha); sn = native_sin(alpha);\n";
    o << "  return mul(a, (real2_t)(cs, sn));\n"
      << "}\n";

    const size_t m = n / radix.value;
    kernel_radix<T>(o, radix);

#ifdef VEXCL_SHOW_KERNELS
    std::cout << o.str() << std::endl;
#endif

    auto program = build_sources(qctx(queue), o.str(), "-cl-mad-enable -cl-fast-relaxed-math");
    cl::Kernel kernel(program, "radix");
    kernel.setArg(0, in);
    kernel.setArg(1, out);
    kernel.setArg<cl_uint>(2, p);

    std::ostringstream desc;
    desc << "dft{r=" << radix << ", p=" << p << ", n=" << n << ", batch=" << batch << ", threads=" << m << "}";

    return kernel_call(desc.str(), program, kernel, cl::NDRange(m, batch), cl::NullRange);
}

template <class T>
kernel_call transpose_kernel(cl::CommandQueue &queue, size_t width, size_t height, cl::Buffer in, cl::Buffer out) {
    std::ostringstream o;
    kernel_common<T>(o);

    // determine max block size to fit into local memory.
    size_t block_size = 128;
    const auto local_size = qdev(queue).getInfo<CL_DEVICE_LOCAL_MEM_SIZE>();
    while(block_size * block_size * sizeof(T) * 2 > local_size) block_size /= 2;
    block_size /= 2;

    // from NVIDIA SDK.
    o << "__kernel void transpose("
      << "__global const real2_t *input, __global real2_t *output, uint width, uint height) {\n"
      << "  const size_t "
      << "    global_x = get_global_id(0), global_y = get_global_id(1),\n"
      << "    local_x = get_local_id(0), local_y = get_local_id(1),\n"
      << "    group_x = get_group_id(0), group_y = get_group_id(1),\n"
      << "    block_size = " << block_size << ",\n"
      << "    target_x = local_y + group_y * block_size,\n"
      << "    target_y = local_x + group_x * block_size;\n"
      << "  const bool range = global_x < width && global_y < height;\n"
        // local memory
      << "  __local real2_t block[" << (block_size * block_size) << "];\n"
        // copy from input to local memory
      << "  if(range)\n"
      << "    block[local_x + local_y * block_size] = input[global_x + global_y * width];\n"
        // wait until the whole block is filled
      << "  barrier(CLK_LOCAL_MEM_FENCE);\n"
        // transpose local block to target
      << "  if(range)\n"
      << "    output[target_x + target_y * height] = block[local_x + local_y * block_size];\n"
      << "}\n";

#ifdef VEXCL_SHOW_KERNELS
    std::cout << o.str() << std::endl;
#endif

    auto program = build_sources(qctx(queue), o.str());
    cl::Kernel kernel(program, "transpose");
    kernel.setArg(0, in);
    kernel.setArg(1, out);
    kernel.setArg<cl_uint>(2, width);
    kernel.setArg<cl_uint>(3, height);

    // range multiple of wg size, last block maybe not completely filled.
    size_t r_w = int_ceil(width, block_size);
    size_t r_h = int_ceil(height, block_size);

    std::ostringstream desc;
    desc << "transpose{"
         << "w=" << width << "(" << r_w << "), "
         << "h=" << height << "(" << r_h << "), "
         << "bs=" << block_size << "}";

    return kernel_call(desc.str(), program, kernel, cl::NDRange(r_w, r_h),
        cl::NDRange(block_size, block_size));
}

} // namespace fft
} // namespace vex

#endif
<commit_msg>Limit transpose block size also by workgroup size<commit_after>#ifndef VEXCL_FFT_KERNELS_HPP
#define VEXCL_FFT_KERNELS_HPP

/*
The MIT License

Copyright (c) 2012-2013 Denis Demidov <ddemidov@ksu.ru>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * \file   fft/kernels.hpp
 * \author Pascal Germroth <pascal@ensieve.org>
 * \brief  Kernel generator for FFT.
 */

// OpenCL FFT kernels, original by Eric Bainville

namespace vex {
namespace fft {

// Store v=b^e as components.
struct pow {
    size_t base, exponent, value;
    pow(size_t b, size_t e) : base(b), exponent(e), value(std::pow(b, e)) {}
};

inline std::ostream &operator<<(std::ostream &o, const pow &p) {
    return o << p.base << '^' << p.exponent << '=' << p.value;
}

/// Reverse the digits of a number in the base given.
/** 0 <= i < m,
 * i is converted into a base m.base-number,
 * its digits reversed, and converted back into an integer. */
size_t digit_reverse(size_t i, pow m) {
    assert(i < m.value);
    // convert to other base, digits[0]=least significant
    // convert back with digits[0]=most significant.
    size_t j = 0;
    for(size_t k = 0 ; k < m.exponent ; k++) {
        j = j * m.base + (i % m.base);
        i /= m.base;
    }
    return j;
}

/// a is a power of 2, b is an arbitrary number.
/** returns a power of 2 <= a that is <= b. */
size_t min_pow2(size_t a, size_t b) {
    while(a > b)
        a >>= 1;
    return a;
}

/// ceil(x/m) * m
size_t int_ceil(size_t x, size_t m) {
    return (x + m - 1) / m * m;
}

struct kernel_call {
    std::string desc;
    cl::Program program;
    cl::Kernel kernel;
    cl::NDRange global, local;
    kernel_call(std::string d, cl::Program p, cl::Kernel k, cl::NDRange g, cl::NDRange l) : desc(d), program(p), kernel(k), global(g), local(l) {}
};

// generates "(prefix vfrom,vfrom+1,...,vto)"
inline void param_list(std::ostringstream &o, std::string prefix, size_t from, size_t to, size_t step = 1) {
    o << '(';
    for(size_t i = from ; i != to ; i += step) {
        if(i != from) o << ", ";
        o << prefix << 'v' << i;
    } o << ')';
}

// omega(n,k) = exp(-i 2 pi k / n)
template<class T>
inline typename cl_vector_of<T,2>::type omega(int k, int n) {
    const T alpha = -2 * M_PI * (k % n) / n;
    return {{std::cos(alpha), std::sin(alpha)}};
}

template <class T>
void in_place_dft(std::ostringstream &o, pow radix) {
    typedef typename cl_vector_of<T,2>::type T2;
    o << "void dft" << radix.value;
    param_list(o, "real2_t *", 0, radix.value);
    o << "{\n";

    if(radix.exponent == 1) { // prime bases
        if(radix.value == 2) {
            o << "  real2_t tmp = *v0 - *v1;\n"
              << "  *v0 += *v1;\n"
              << "  *v1 = tmp;\n";
        } else {
            // naive DFT
            for(size_t i = 1 ; i < radix.base ; i++)
                o << "  real2_t w" << i << " = " << omega<T>(i, radix.base) << ";\n";
            for(size_t i = 0 ; i < radix.base ; i++) {
                o << "  real2_t t" << i << " = *v0";
                for(size_t j = 1 ; j < radix.base ; j++) {
                    o << " + ";
                    if(i == 0)
                        o << "*v" << j;
                    else
                        o << "mul(*v" << j << ", w" << ((i * j) % radix.base) << ")";
                }
                o << ";\n";
            }
            // write back
            for(size_t i = 0 ; i < radix.base ; i++)
                o << "  *v" << i << " = t" << i << ";\n";
        }
    } else { // recursive prime power DFTs
        const size_t prev_radix = radix.value / radix.base;
        // leaves
        for(size_t i = 0 ; i < prev_radix ; i++) {
            o << "  dft" << radix.base;
            param_list(o, "", i, i + radix.value, prev_radix);
            o << ";\n";
        }
        // twiddle
        for(size_t i = 1 ; i <= (prev_radix-1) * (radix.base-1) ; i++)
            o << "  real2_t w" << i << " = " << omega<T>(i, radix.value) << ";\n";
        for(size_t i = 1 ; i < prev_radix ; i++) {
            for(size_t j = 1 ; j < radix.base ; j++) {
                const size_t k = i + j * prev_radix;
                o << "  *v" << k << " = mul(*v" << k << ", w" << ((i * j) % radix.value) << ");\n";
            }
        }
        // next stage
        for(size_t i = 0 ; i < radix.base ; i++) {
            o << "  dft" << prev_radix;
            param_list(o, "", i * prev_radix, (i + 1) * prev_radix);
            o << ";\n";
        }
    }
    o << "}\n";
}

template <class T>
void kernel_radix(std::ostringstream &o, pow radix) {
    for(size_t e = 1 ; e <= radix.exponent ; e++)
        in_place_dft<T>(o, pow(radix.base, e));
    // kernel.
    o << "__kernel void radix(__global const real2_t *x, __global real2_t *y, uint p) {\n"
      << "  const size_t i = get_global_id(0);\n"
      << "  const size_t threads = get_global_size(0);\n"
        // index in input sequence, in 0..P-1
      << "  const size_t k = i % p;\n"
      << "  const size_t batch_offset = get_global_id(1) * threads * " << radix.value << ";\n";

    // read
    o << "  x += i + batch_offset;\n";
    for(size_t i = 0 ; i < radix.value ; i++)
        o << "  real2_t v" << i << " = x[" << i << " * threads];\n";

    // twiddle
    o << "  if(p != 1) {\n";
    for(size_t i = 1 ; i < radix.value ; i++) {
        const T alpha = -2 * M_PI * i / radix.value;
        o << "    v" << i << " = twiddle(v" << i << ", "
          << "(real_t)" << alpha << " * k / p);\n";
    }
    o << "  }\n";

    // inplace DFT
    o << "  dft" << radix.value;
    param_list(o, "&", 0, radix.value);
    o << ";\n";

    // write back
    o << "  const size_t j = k + (i - k) * " << radix.value << ";\n";
    o << "  y += j + batch_offset;\n";
    for(size_t i = 0 ; i < radix.value ; i++)
        o << "  y[" << i << " * p] = v" << digit_reverse(i, radix) << ";\n";
    o << "}\n";
}

template <class T>
void kernel_common(std::ostringstream &o) {
    if(std::is_same<T, cl_double>::value) {
        o << standard_kernel_header
          << "typedef double real_t;\n"
          << "typedef double2 real2_t;\n";
    } else {
        o << "typedef float real_t;\n"
          << "typedef float2 real2_t;\n";
    }
}

template <class T>
kernel_call radix_kernel(cl::CommandQueue &queue, size_t n, size_t batch, bool invert, pow radix, size_t p, cl::Buffer in, cl::Buffer out) {
    std::ostringstream o;
    o << std::setprecision(25);
    kernel_common<T>(o);

    // Return A*B (complex multiplication)
    o << "real2_t mul(real2_t a, real2_t b) {\n";
    if(invert) // conjugate b
        o << "  return (real2_t)(a.x * b.x + a.y * b.y, a.y * b.x - a.x * b.y);\n";
    else
        o << "  return (real2_t)(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);\n";
    o << "}\n";

    // A * exp(alpha * I) == A  * (cos(alpha) + I * sin(alpha))
    // native_cos(), native_sin() is a *lot* faster than sincos, on nVidia.
    o << "real2_t twiddle(real2_t a, real_t alpha) {\n";
    o << "  real_t cs, sn;\n";
    if(std::is_same<T, cl_double>::value)
        // use sincos with double since we probably want higher precision
        o << "  sn = sincos(alpha, &cs);\n";
    else
        // use native with float since we probably want higher performance
        o << "  cs = native_cos(alpha); sn = native_sin(alpha);\n";
    o << "  return mul(a, (real2_t)(cs, sn));\n"
      << "}\n";

    const size_t m = n / radix.value;
    kernel_radix<T>(o, radix);

#ifdef VEXCL_SHOW_KERNELS
    std::cout << o.str() << std::endl;
#endif

    auto program = build_sources(qctx(queue), o.str(), "-cl-mad-enable -cl-fast-relaxed-math");
    cl::Kernel kernel(program, "radix");
    kernel.setArg(0, in);
    kernel.setArg(1, out);
    kernel.setArg<cl_uint>(2, p);

    std::ostringstream desc;
    desc << "dft{r=" << radix << ", p=" << p << ", n=" << n << ", batch=" << batch << ", threads=" << m << "}";

    return kernel_call(desc.str(), program, kernel, cl::NDRange(m, batch), cl::NullRange);
}

template <class T>
kernel_call transpose_kernel(cl::CommandQueue &queue, size_t width, size_t height, cl::Buffer in, cl::Buffer out) {
    std::ostringstream o;
    kernel_common<T>(o);

    // determine max block size to fit into local memory/workgroup
    size_t block_size = 128;
    {
        const auto dev = qdev(queue);
        const auto local_size = dev.getInfo<CL_DEVICE_LOCAL_MEM_SIZE>();
        const auto workgroup = dev.getInfo<CL_DEVICE_MAX_WORK_GROUP_SIZE>();
        while(block_size * block_size * sizeof(T) * 2 > local_size) block_size /= 2;
        while(block_size * block_size > workgroup) block_size /= 2;
    }

    // from NVIDIA SDK.
    o << "__kernel void transpose("
      << "__global const real2_t *input, __global real2_t *output, uint width, uint height) {\n"
      << "  const size_t "
      << "    global_x = get_global_id(0), global_y = get_global_id(1),\n"
      << "    local_x = get_local_id(0), local_y = get_local_id(1),\n"
      << "    group_x = get_group_id(0), group_y = get_group_id(1),\n"
      << "    block_size = " << block_size << ",\n"
      << "    target_x = local_y + group_y * block_size,\n"
      << "    target_y = local_x + group_x * block_size;\n"
      << "  const bool range = global_x < width && global_y < height;\n"
        // local memory
      << "  __local real2_t block[" << (block_size * block_size) << "];\n"
        // copy from input to local memory
      << "  if(range)\n"
      << "    block[local_x + local_y * block_size] = input[global_x + global_y * width];\n"
        // wait until the whole block is filled
      << "  barrier(CLK_LOCAL_MEM_FENCE);\n"
        // transpose local block to target
      << "  if(range)\n"
      << "    output[target_x + target_y * height] = block[local_x + local_y * block_size];\n"
      << "}\n";

#ifdef VEXCL_SHOW_KERNELS
    std::cout << o.str() << std::endl;
#endif

    auto program = build_sources(qctx(queue), o.str());
    cl::Kernel kernel(program, "transpose");
    kernel.setArg(0, in);
    kernel.setArg(1, out);
    kernel.setArg<cl_uint>(2, width);
    kernel.setArg<cl_uint>(3, height);

    // range multiple of wg size, last block maybe not completely filled.
    size_t r_w = int_ceil(width, block_size);
    size_t r_h = int_ceil(height, block_size);

    std::ostringstream desc;
    desc << "transpose{"
         << "w=" << width << "(" << r_w << "), "
         << "h=" << height << "(" << r_h << "), "
         << "bs=" << block_size << "}";

    return kernel_call(desc.str(), program, kernel, cl::NDRange(r_w, r_h),
        cl::NDRange(block_size, block_size));
}

} // namespace fft
} // namespace vex

#endif
<|endoftext|>


<commit_before>#ifndef VEXCL_FFT_KERNELS_HPP
#define VEXCL_FFT_KERNELS_HPP

/*
The MIT License

Copyright (c) 2012-2013 Denis Demidov <ddemidov@ksu.ru>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * \file   fft/kernels.hpp
 * \author Pascal Germroth <pascal@ensieve.org>
 * \brief  Kernel generator for FFT.
 */

// OpenCL FFT kernels, original by Eric Bainville

namespace vex {
namespace fft {

struct kernel_call {
    cl::Program program;
    cl::Kernel kernel;
    cl::NDRange global, local;
    kernel_call(cl::Program p, cl::Kernel k, cl::NDRange g, cl::NDRange l) : program(p), kernel(k), global(g), local(l) {}
};

/// Return the greatest k = 2^m <= n
static int pow2_floor(int n) {
    if(n == 0) return 0;
    for(size_t m = 0 ; ; m++)
        if((1 << (m + 1)) > n)
            return 1 << m;
}

// return the `s` most significant bits from an integer in reverse.
// <http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious>
template <class T>
T bit_reverse(T v, size_t s = sizeof(T) * CHAR_BIT) {
    T r = v & 1;
    s--;
    for(v >>= 1 ; v ; v >>= 1) {
        r <<= 1;
        r |= v & 1;
        s--;
    }
    return r << s;
}

// calculate log2(2^k) = k.
template <class T>
T log2(T n) {
    int k = 0;
    if(n <= 0) throw std::logic_error("not a positive power of 2.");
    while(n != 1) {
        if(n & 1) throw std::logic_error("not a power of 2.");
        n >>= 1;
        k++;
    }
    return k;
}

// generates "(vfrom,vfrom+1,...,vto)"
void param_list(std::ostringstream &o, size_t from, size_t to) {
    o << '(';
    for(size_t i = from ; i != to ; i++) {
        if(i != from) o << ',';
        o << 'v' << i;
    } o << ')';
}

template <class T>
void in_place_dft(std::ostringstream &o, bool invert, size_t radix) {
    // inline DFT macro.
    if(radix == 2) {
        o << R"(#define DFT2(v0,v1) { \
                real2_t tmp = v0 - v1; \
                v0 += v1; \
                v1 = tmp; \
            }
        )";
    } else {
        const size_t half_radix = radix / 2;
        // parameters
        o << "#define DFT" << radix;
        param_list(o, 0, radix);
        o << '{';
        // leaves
        for(size_t i = 0 ; i < half_radix ; i++) {
            o << "DFT2(v" << i << ",v" << (i + half_radix) << ");";
            if(i != 0) {
                const size_t j = i + half_radix;
                if(2 * i == half_radix) {
                    o << 'v' << j << "=twiddle_1_2(v" << j << ");";
                } else {
                    T factor = (invert ? 1 : -1) * (T)M_PI * i / half_radix;
                    typename cl_vector_of<T,2>::type twiddle =
                        {{std::cos(factor), std::sin(factor)}};
                    o << 'v' << j << "=mul(v" << j << ',' << std::setprecision(25) << twiddle << ");";
                }
            }
        }
        // next stage
        o << "DFT" << half_radix; param_list(o, 0, half_radix); o << ';';
        o << "DFT" << half_radix ; param_list(o, half_radix, radix); o << ';';
        o << "}\n";
    }
}

template <class T>
void kernel_radix(std::ostringstream &o, bool invert, size_t radix, size_t p, size_t threads) {
    for(size_t r = radix ; r >= 2 ; r /= 2)
        in_place_dft<T>(o, invert, r);

    // kernel.
    o << "__kernel void radix(__global const real2_t *x, __global real2_t *y) {";
    o << "const size_t i = get_global_id(0);"
        << "const size_t k = i & " << (p - 1) << ";" // index in input sequence, in 0..P-1
        << "const size_t j = ((i - k) * " << radix << ") + k;" // output index
        << "const size_t batch_offset = get_global_id(1) * " << (threads * radix) << ';'
        << "x += i + batch_offset; y += j + batch_offset;"
        << "real_t alpha = -FFT_PI / " << (p * radix / 2) << " * k;";

    // read
    o << "real2_t v0 = x[0];";
    for(size_t i = 1 ; i < radix ; i++)
        o << "real2_t v" << i << "=twiddle(x[" << (i * threads) << "]," << i << ",alpha);";
    // inplace DFT
    o << "DFT" << radix; param_list(o, 0, radix); o << ';';
    // write back
    for(size_t i = 0 ; i < radix ; i++) {
        size_t j = bit_reverse(i, log2(radix));
        o << "y[" << (i * p) << "]=v" << j << ';';
    }
    o << "}\n";
}

template <class T>
void kernel_common(std::ostringstream &o) {
    if(std::is_same<T, cl_double>::value) {
        o << standard_kernel_header;
        o << "typedef double real_t; typedef double2 real2_t;\n";
        o << "#define FFT_PI M_PI\n";
    } else {
        o << "typedef float real_t; typedef float2 real2_t;\n";
        o << "#define FFT_PI M_PI_F\n";
    }
}

template <class T>
kernel_call radix_kernel(cl::CommandQueue &queue, size_t n, size_t batch, bool invert, size_t radix, size_t p, cl::Buffer in, cl::Buffer out) {
    std::ostringstream o;
    kernel_common<T>(o);

    // Return A*B (complex multiplication)
    o << R"(
        real2_t mul(real2_t a, real2_t b) {
            return (real2_t)(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
        }
    )";

    // A * exp(k * alpha * I) == cos(k * alpha) + I * sin(k * alpha)
    o << "real2_t twiddle(real2_t a, int k, real_t alpha) {"
        << "real_t cs, sn;"
        << "sn = sincos(" << (invert ? '-' : ' ') << "k * alpha, &cs);"
        << "return mul(a, (real2_t)(cs, sn));"
        << "}";

    // real(twiddle) = 0
    o << "real2_t twiddle_1_2(real2_t a){"
        << "return (real2_t)(" << (invert ? "-a.y, a.x" : "a.y, -a.x") << ");}\n";

    const size_t m = n / radix;
    kernel_radix<T>(o, invert, radix, p, m);

    auto program = build_sources(qctx(queue), o.str());
    cl::Kernel kernel(program, "radix");
    kernel.setArg(0, in);
    kernel.setArg(1, out);

    size_t wg = pow2_floor(std::min(m,
        (size_t)kernel_workgroup_size(kernel, qdev(queue))));

    return kernel_call(program, kernel, cl::NDRange(m, batch), cl::NDRange(wg, 1));
}

template <class T>
kernel_call transpose_kernel(cl::CommandQueue &queue, size_t width, size_t height, cl::Buffer in, cl::Buffer out) {
    std::ostringstream o;
    kernel_common<T>(o);

    // determine max block size to fit into __local memory.
    size_t block_size = 128;
    const auto local_size = qdev(queue).getInfo<CL_DEVICE_LOCAL_MEM_SIZE>();
    while(block_size * block_size * sizeof(T) * 2 > local_size) block_size /= 2;
    block_size /= 2;

    // from NVIDIA SDK.
    o << "__kernel void transpose("
        "__global const real2_t *input, __global real2_t *output) {"
        "const size_t "
        "global_x = get_global_id(0), global_y = get_global_id(1),"
        "local_x = get_local_id(0), local_y = get_local_id(1),"
        "group_x = get_group_id(0), group_y = get_group_id(1),"
        "width =" << width << ", height = " << height << ", block_size = " << block_size << ","
        "target_x = local_y + group_y * block_size,"
        "target_y = local_x + group_x * block_size;"
        // local memory
        "__local real2_t block[" << (block_size * block_size) << "];"
        // copy from input to local memory
        "block[local_x + local_y * block_size] = input[global_x + global_y * width];"
        // wait until the whole block is filled
        "barrier(CLK_LOCAL_MEM_FENCE);"
        // transpose local block to target
        "output[target_x + target_y * height] = block[local_x + local_y * block_size];"
        "}";

    auto program = build_sources(qctx(queue), o.str());
    cl::Kernel kernel(program, "transpose");
    kernel.setArg(0, in);
    kernel.setArg(1, out);

    return kernel_call(program, kernel, cl::NDRange(width, height),
        cl::NDRange(std::min(width, block_size), std::min(height, block_size)));
}

} // namespace fft
} // namespace vex

#endif
<commit_msg>More compile time constants.<commit_after>#ifndef VEXCL_FFT_KERNELS_HPP
#define VEXCL_FFT_KERNELS_HPP

/*
The MIT License

Copyright (c) 2012-2013 Denis Demidov <ddemidov@ksu.ru>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * \file   fft/kernels.hpp
 * \author Pascal Germroth <pascal@ensieve.org>
 * \brief  Kernel generator for FFT.
 */

// OpenCL FFT kernels, original by Eric Bainville

namespace vex {
namespace fft {

struct kernel_call {
    cl::Program program;
    cl::Kernel kernel;
    cl::NDRange global, local;
    kernel_call(cl::Program p, cl::Kernel k, cl::NDRange g, cl::NDRange l) : program(p), kernel(k), global(g), local(l) {}
};

/// Return the greatest k = 2^m <= n
static int pow2_floor(int n) {
    if(n == 0) return 0;
    for(size_t m = 0 ; ; m++)
        if((1 << (m + 1)) > n)
            return 1 << m;
}

// return the `s` most significant bits from an integer in reverse.
// <http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious>
template <class T>
T bit_reverse(T v, size_t s = sizeof(T) * CHAR_BIT) {
    T r = v & 1;
    s--;
    for(v >>= 1 ; v ; v >>= 1) {
        r <<= 1;
        r |= v & 1;
        s--;
    }
    return r << s;
}

// calculate log2(2^k) = k.
template <class T>
T log2(T n) {
    int k = 0;
    if(n <= 0) throw std::logic_error("not a positive power of 2.");
    while(n != 1) {
        if(n & 1) throw std::logic_error("not a power of 2.");
        n >>= 1;
        k++;
    }
    return k;
}

// generates "(vfrom,vfrom+1,...,vto)"
void param_list(std::ostringstream &o, size_t from, size_t to) {
    o << '(';
    for(size_t i = from ; i != to ; i++) {
        if(i != from) o << ',';
        o << 'v' << i;
    } o << ')';
}

template <class T>
void in_place_dft(std::ostringstream &o, bool invert, size_t radix) {
    // inline DFT macro.
    if(radix == 2) {
        o << R"(#define DFT2(v0,v1) { \
                real2_t tmp = v0 - v1; \
                v0 += v1; \
                v1 = tmp; \
            }
        )";
    } else {
        const size_t half_radix = radix / 2;
        // parameters
        o << "#define DFT" << radix;
        param_list(o, 0, radix);
        o << '{';
        // leaves
        for(size_t i = 0 ; i < half_radix ; i++) {
            o << "DFT2(v" << i << ",v" << (i + half_radix) << ");";
            if(i != 0) {
                const size_t j = i + half_radix;
                if(2 * i == half_radix) {
                    o << 'v' << j << "=twiddle_1_2(v" << j << ");";
                } else {
                    T factor = (invert ? 1 : -1) * (T)M_PI * i / half_radix;
                    typename cl_vector_of<T,2>::type twiddle =
                        {{std::cos(factor), std::sin(factor)}};
                    o << 'v' << j << "=mul(v" << j << ',' << std::setprecision(25) << twiddle << ");";
                }
            }
        }
        // next stage
        o << "DFT" << half_radix; param_list(o, 0, half_radix); o << ';';
        o << "DFT" << half_radix ; param_list(o, half_radix, radix); o << ';';
        o << "}\n";
    }
}

template <class T>
void kernel_radix(std::ostringstream &o, bool invert, size_t radix, size_t p, size_t threads) {
    for(size_t r = radix ; r >= 2 ; r /= 2)
        in_place_dft<T>(o, invert, r);

    // kernel.
    o << "__kernel void radix(__global const real2_t *x, __global real2_t *y) {";
    o << "const size_t i = get_global_id(0);"
        << "const size_t k = i & " << (p - 1) << ";" // index in input sequence, in 0..P-1
        << "const size_t j = ((i - k) * " << radix << ") + k;" // output index
        << "const size_t batch_offset = get_global_id(1) * " << (threads * radix) << ';'
        << "x += i + batch_offset; y += j + batch_offset;";

    // read
    for(size_t i = 0 ; i < radix ; i++)
        o << "real2_t v" << i << " = x[" << (i * threads) << "];";
    // twiddle
    for(size_t i = 1 ; i < radix ; i++) {
        const T alpha = -M_PI * i / (p * radix / 2);
        o << "v" << i << "=twiddle(v" << i << ",(real_t)" << std::setprecision(25) << alpha << " * k);";
    }
    // inplace DFT
    o << "DFT" << radix; param_list(o, 0, radix); o << ';';
    // write back
    for(size_t i = 0 ; i < radix ; i++) {
        size_t j = bit_reverse(i, log2(radix));
        o << "y[" << (i * p) << "]=v" << j << ';';
    }
    o << "}\n";
}

template <class T>
void kernel_common(std::ostringstream &o) {
    if(std::is_same<T, cl_double>::value) {
        o << standard_kernel_header;
        o << "typedef double real_t; typedef double2 real2_t;\n";
        o << "#define FFT_PI M_PI\n";
    } else {
        o << "typedef float real_t; typedef float2 real2_t;\n";
        o << "#define FFT_PI M_PI_F\n";
    }
}

template <class T>
kernel_call radix_kernel(cl::CommandQueue &queue, size_t n, size_t batch, bool invert, size_t radix, size_t p, cl::Buffer in, cl::Buffer out) {
    std::ostringstream o;
    kernel_common<T>(o);

    // Return A*B (complex multiplication)
    o << R"(
        real2_t mul(real2_t a, real2_t b) {
            return (real2_t)(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
        }
    )";

    // A * exp(alpha * I) == A  * (cos(alpha) + I * sin(alpha))
    o << "real2_t twiddle(real2_t a, real_t alpha) {"
        << "real_t cs, sn;"
        << "sn = sincos(" << (invert ? '-' : ' ') << "alpha, &cs);"
        << "return mul(a, (real2_t)(cs, sn));"
        << "}";

    // real(twiddle) = 0
    o << "real2_t twiddle_1_2(real2_t a){"
        << "return (real2_t)(" << (invert ? "-a.y, a.x" : "a.y, -a.x") << ");}\n";

    const size_t m = n / radix;
    kernel_radix<T>(o, invert, radix, p, m);

    auto program = build_sources(qctx(queue), o.str(), "-cl-mad-enable -cl-fast-relaxed-math");
    cl::Kernel kernel(program, "radix");
    kernel.setArg(0, in);
    kernel.setArg(1, out);

    size_t wg = pow2_floor(std::min(m,
        (size_t)kernel_workgroup_size(kernel, qdev(queue))));

    return kernel_call(program, kernel, cl::NDRange(m, batch), cl::NDRange(wg, 1));
}

template <class T>
kernel_call transpose_kernel(cl::CommandQueue &queue, size_t width, size_t height, cl::Buffer in, cl::Buffer out) {
    std::ostringstream o;
    kernel_common<T>(o);

    // determine max block size to fit into __local memory.
    size_t block_size = 128;
    const auto local_size = qdev(queue).getInfo<CL_DEVICE_LOCAL_MEM_SIZE>();
    while(block_size * block_size * sizeof(T) * 2 > local_size) block_size /= 2;
    block_size /= 2;

    // from NVIDIA SDK.
    o << "__kernel void transpose("
        "__global const real2_t *input, __global real2_t *output) {"
        "const size_t "
        "global_x = get_global_id(0), global_y = get_global_id(1),"
        "local_x = get_local_id(0), local_y = get_local_id(1),"
        "group_x = get_group_id(0), group_y = get_group_id(1),"
        "width =" << width << ", height = " << height << ", block_size = " << block_size << ","
        "target_x = local_y + group_y * block_size,"
        "target_y = local_x + group_x * block_size;"
        // local memory
        "__local real2_t block[" << (block_size * block_size) << "];"
        // copy from input to local memory
        "block[local_x + local_y * block_size] = input[global_x + global_y * width];"
        // wait until the whole block is filled
        "barrier(CLK_LOCAL_MEM_FENCE);"
        // transpose local block to target
        "output[target_x + target_y * height] = block[local_x + local_y * block_size];"
        "}";

    auto program = build_sources(qctx(queue), o.str());
    cl::Kernel kernel(program, "transpose");
    kernel.setArg(0, in);
    kernel.setArg(1, out);

    return kernel_call(program, kernel, cl::NDRange(width, height),
        cl::NDRange(std::min(width, block_size), std::min(height, block_size)));
}

} // namespace fft
} // namespace vex

#endif
<|endoftext|>


<commit_before>// Copyright (c) 2011-2012 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

#include "Terminal.h"
#include "NamedPipe.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <string>

#include "../shared/DebugClient.h"

#define CSI "\x1b["

const int COLOR_ATTRIBUTE_MASK =
        FOREGROUND_BLUE |
        FOREGROUND_GREEN |
        FOREGROUND_RED |
        FOREGROUND_INTENSITY |
        BACKGROUND_BLUE |
        BACKGROUND_GREEN |
        BACKGROUND_RED |
        BACKGROUND_INTENSITY;

const int FLAG_RED    = 1;
const int FLAG_GREEN  = 2;
const int FLAG_BLUE   = 4;
const int FLAG_BRIGHT = 8;

const int BLACK  = 0;
const int DKGRAY = BLACK | FLAG_BRIGHT;
const int LTGRAY = FLAG_RED | FLAG_GREEN | FLAG_BLUE;
const int WHITE  = LTGRAY | FLAG_BRIGHT;

// SGR parameters (Select Graphic Rendition)
const int SGR_FORE = 30;
const int SGR_FORE_HI = 90;
const int SGR_BACK = 40;
const int SGR_BACK_HI = 100;

// Work around the old MinGW, which lacks COMMON_LVB_LEADING_BYTE and
// COMMON_LVB_TRAILING_BYTE.
const int WINPTY_COMMON_LVB_LEADING_BYTE = 0x100;
const int WINPTY_COMMON_LVB_TRAILING_BYTE = 0x200;

namespace {

static void outUInt(std::string &out, unsigned int n)
{
    char buf[32];
    char *pbuf = &buf[32];
    *(--pbuf) = '\0';
    do {
        *(--pbuf) = '0' + n % 10;
        n /= 10;
    } while (n != 0);
    out.append(pbuf);
}

static void outputSetColorSgrParams(std::string &out, bool isFore, int color)
{
    out.push_back(';');
    const int sgrBase = isFore ? SGR_FORE : SGR_BACK;
    if (color & FLAG_BRIGHT) {
        // Some terminals don't support the 9X/10X "intensive" color parameters
        // (e.g. the Eclipse TM terminal as of this writing).  Those terminals
        // will quietly ignore a 9X/10X code, and the other terminals will
        // ignore a 3X/4X code if it's followed by a 9X/10X code.  Therefore,
        // output a 3X/4X code as a fallback, then override it.
        const int colorBase = color & ~FLAG_BRIGHT;
        outUInt(out, sgrBase + colorBase);
        out.push_back(';');
        outUInt(out, sgrBase + (SGR_FORE_HI - SGR_FORE) + colorBase);
    } else {
        outUInt(out, sgrBase + color);
    }
}

} // anonymous namespace

Terminal::Terminal(NamedPipe *output) :
    m_output(output),
    m_remoteLine(0),
    m_cursorHidden(false),
    m_remoteColor(-1),
    m_consoleMode(false)
{
}

void Terminal::setConsoleMode(int mode)
{
    if (mode == 1)
        m_consoleMode = true;
    else
        m_consoleMode = false;
}

void Terminal::reset(bool sendClearFirst, int newLine)
{
    if (sendClearFirst && !m_consoleMode) {
        // 0m   ==> reset SGR parameters
        // 1;1H ==> move cursor to top-left position
        // 2J   ==> clear the entire screen
        m_output->write(CSI"0m" CSI"1;1H" CSI"2J");
    }
    m_remoteLine = newLine;
    m_cursorHidden = false;
    m_cursorPos = std::pair<int, int>(0, newLine);
    m_remoteColor = -1;
}

void Terminal::sendLine(int line, CHAR_INFO *lineData, int width)
{
    hideTerminalCursor();
    moveTerminalToLine(line);

    // Erase in Line -- erase entire line.
    if (!m_consoleMode)
        m_output->write(CSI"2K");

    m_termLine.clear();

    int length = 0;
    for (int i = 0; i < width; ++i) {
        int color = lineData[i].Attributes & COLOR_ATTRIBUTE_MASK;
        if (color != m_remoteColor && !m_consoleMode) {
            int fore = 0;
            int back = 0;
            if (color & FOREGROUND_RED)       fore |= FLAG_RED;
            if (color & FOREGROUND_GREEN)     fore |= FLAG_GREEN;
            if (color & FOREGROUND_BLUE)      fore |= FLAG_BLUE;
            if (color & FOREGROUND_INTENSITY) fore |= FLAG_BRIGHT;
            if (color & BACKGROUND_RED)       back |= FLAG_RED;
            if (color & BACKGROUND_GREEN)     back |= FLAG_GREEN;
            if (color & BACKGROUND_BLUE)      back |= FLAG_BLUE;
            if (color & BACKGROUND_INTENSITY) back |= FLAG_BRIGHT;

            // Translate the fore/back colors into terminal escape codes using
            // a heuristic that works OK with common white-on-black or
            // black-on-white color schemes.  We don't know which color scheme
            // the terminal is using.  It is ugly to force white-on-black text
            // on a black-on-white terminal, and it's even ugly to force the
            // matching scheme.  It's probably relevant that the default
            // fore/back terminal colors frequently do not match any of the 16
            // palette colors.

            // Typical default terminal color schemes (according to palette,
            // when possible):
            //  - mintty:               LtGray-on-Black(A)
            //  - putty:                LtGray-on-Black(A)
            //  - xterm:                LtGray-on-Black(A)
            //  - Konsole:              LtGray-on-Black(A)
            //  - JediTerm/JetBrains:   Black-on-White(B)
            //  - rxvt:                 Black-on-White(B)

            // If the background is the default color (black), then it will
            // map to Black(A) or White(B).  If we translate White to White,
            // then a Black background and a White background in the console
            // are both White with (B).  Therefore, we should translate White
            // using SGR 7 (Invert).  The typical finished mapping table for
            // background grayscale colors is:
            //
            //  (A) White => LtGray(fore)
            //  (A) Black => Black(back)
            //  (A) LtGray => LtGray
            //  (A) DkGray => DkGray
            //
            //  (B) White => Black(fore)
            //  (B) Black => White(back)
            //  (B) LtGray => LtGray
            //  (B) DkGray => DkGray
            //

            m_termLine.append(CSI"0");
            if (back == BLACK) {
                if (fore == LTGRAY) {
                    // The "default" foreground color.  Use the terminal's
                    // default colors.
                } else if (fore == WHITE) {
                    // Sending the literal color white would behave poorly if
                    // the terminal were black-on-white.  Sending Bold is not
                    // guaranteed to alter the color, but it will make the text
                    // visually distinct, so do that instead.
                    m_termLine.append(";1");
                } else if (fore == DKGRAY) {
                    // Set the foreground color to DkGray(90) with a fallback
                    // of LtGray(37) for terminals that don't handle the 9X SGR
                    // parameters (e.g. Eclipse's TM Terminal as of this
                    // writing).
                    m_termLine.append(";37;90");
                } else {
                    outputSetColorSgrParams(m_termLine, true, fore);
                }
            } else if (back == WHITE) {
                // Set the background color using Invert on the default
                // foreground color, and set the foreground color by setting a
                // background color.

                // Use the terminal's inverted colors.
                m_termLine.append(";7");
                if (fore == LTGRAY || fore == BLACK) {
                    // We're likely mapping Console White to terminal LtGray or
                    // Black.  If they are the Console foreground color, then
                    // don't set a terminal foreground color to avoid creating
                    // invisible text.
                } else {
                    outputSetColorSgrParams(m_termLine, false, fore);
                }
            } else {
                // Set the foreground and background to match exactly that in
                // the Windows console.
                outputSetColorSgrParams(m_termLine, true, fore);
                outputSetColorSgrParams(m_termLine, false, back);
            }
            if (fore == back) {
                // The foreground and background colors are exactly equal, so
                // attempt to hide the text using the Conceal SGR parameter,
                // which some terminals support.
                m_termLine.append(";8");
            }
            m_termLine.push_back('m');
            length = m_termLine.size();
        }
        m_remoteColor = color;
        if (lineData[i].Attributes & WINPTY_COMMON_LVB_TRAILING_BYTE) {
            // CJK full-width characters occupy two console cells.  The first
            // cell is marked with COMMON_LVB_LEADING_BYTE, and the second is
            // marked with COMMON_LVB_TRAILING_BYTE.  Skip the trailing cells.
            continue;
        }
        // TODO: Is it inefficient to call WideCharToMultiByte once per
        // character?

        wchar_t ch = lineData[i].Char.UnicodeChar;

        // The Windows Console has a popup window (e.g. that appears with F7)
        // that is sometimes bordered with box-drawing characters.  With the
        // Japanese and Korean system locales (CP932 and CP949), the
        // UnicodeChar values for the box-drawing characters are 1..6.  Detect
        // this and map the values to the correct Unicode values.
        //
        // N.B. In the English locale, the UnicodeChar values are correct, and
        // they identify single-line characters rather than double-line.  In
        // the Chinese Simplified and Traditional locales, the popups use ASCII
        // characters instead.
        if (ch <= 6) {
            switch (ch) {
                case 1: ch = 0x2554; break; // BOX DRAWINGS DOUBLE DOWN AND RIGHT
                case 2: ch = 0x2557; break; // BOX DRAWINGS DOUBLE DOWN AND LEFT
                case 3: ch = 0x255A; break; // BOX DRAWINGS DOUBLE UP AND RIGHT
                case 4: ch = 0x255D; break; // BOX DRAWINGS DOUBLE UP AND LEFT
                case 5: ch = 0x2551; break; // BOX DRAWINGS DOUBLE VERTICAL
                case 6: ch = 0x2550; break; // BOX DRAWINGS DOUBLE HORIZONTAL
            }
        }

        char mbstr[16];
        int mblen = WideCharToMultiByte(CP_UTF8,
                                        0,
                                        &ch,
                                        1,
                                        mbstr,
                                        sizeof(mbstr),
                                        NULL,
                                        NULL);
        if (mblen <= 0) {
            mbstr[0] = '?';
            mblen = 1;
        }
        if (mblen == 1 && mbstr[0] == ' ') {
            m_termLine.push_back(' ');
        } else {
            m_termLine.append(mbstr, mblen);
            length = m_termLine.size();
        }
    }

    m_output->write(m_termLine.data(), length);
}

void Terminal::finishOutput(const std::pair<int, int> &newCursorPos)
{
    if (newCursorPos != m_cursorPos)
        hideTerminalCursor();
    if (m_cursorHidden) {
        moveTerminalToLine(newCursorPos.second);
        char buffer[32];
        sprintf(buffer, CSI"%dG" CSI"?25h", newCursorPos.first + 1);
        if (!m_consoleMode)
            m_output->write(buffer);
        m_cursorHidden = false;
    }
    m_cursorPos = newCursorPos;
}

void Terminal::hideTerminalCursor()
{
    if (m_cursorHidden)
        return;
    if (!m_consoleMode)
        m_output->write(CSI"?25l");
    m_cursorHidden = true;
}

void Terminal::moveTerminalToLine(int line)
{
    // Do not use CPL or CNL.  Konsole 2.5.4 does not support Cursor Previous
    // Line (CPL) -- there are "Undecodable sequence" errors.  gnome-terminal
    // 2.32.0 does handle it.  Cursor Next Line (CNL) does nothing if the
    // cursor is on the last line already.

    if (line < m_remoteLine) {
        // CUrsor Up (CUU)
        char buffer[32];
        sprintf(buffer, "\r" CSI"%dA", m_remoteLine - line);
        if (!m_consoleMode)
            m_output->write(buffer);
        m_remoteLine = line;
    } else if (line > m_remoteLine) {
        while (line > m_remoteLine) {
            if (!m_consoleMode)
                m_output->write("\r\n");
            m_remoteLine++;
        }
    } else {
        m_output->write("\r");
    }
}
<commit_msg>Factor out outputSetColor and fixConsolePopupBoxArt functions.<commit_after>// Copyright (c) 2011-2012 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

#include "Terminal.h"
#include "NamedPipe.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <string>

#include "../shared/DebugClient.h"

#define CSI "\x1b["

const int COLOR_ATTRIBUTE_MASK =
        FOREGROUND_BLUE |
        FOREGROUND_GREEN |
        FOREGROUND_RED |
        FOREGROUND_INTENSITY |
        BACKGROUND_BLUE |
        BACKGROUND_GREEN |
        BACKGROUND_RED |
        BACKGROUND_INTENSITY;

const int FLAG_RED    = 1;
const int FLAG_GREEN  = 2;
const int FLAG_BLUE   = 4;
const int FLAG_BRIGHT = 8;

const int BLACK  = 0;
const int DKGRAY = BLACK | FLAG_BRIGHT;
const int LTGRAY = FLAG_RED | FLAG_GREEN | FLAG_BLUE;
const int WHITE  = LTGRAY | FLAG_BRIGHT;

// SGR parameters (Select Graphic Rendition)
const int SGR_FORE = 30;
const int SGR_FORE_HI = 90;
const int SGR_BACK = 40;
const int SGR_BACK_HI = 100;

// Work around the old MinGW, which lacks COMMON_LVB_LEADING_BYTE and
// COMMON_LVB_TRAILING_BYTE.
const int WINPTY_COMMON_LVB_LEADING_BYTE = 0x100;
const int WINPTY_COMMON_LVB_TRAILING_BYTE = 0x200;

namespace {

static void outUInt(std::string &out, unsigned int n)
{
    char buf[32];
    char *pbuf = &buf[32];
    *(--pbuf) = '\0';
    do {
        *(--pbuf) = '0' + n % 10;
        n /= 10;
    } while (n != 0);
    out.append(pbuf);
}

static void outputSetColorSgrParams(std::string &out, bool isFore, int color)
{
    out.push_back(';');
    const int sgrBase = isFore ? SGR_FORE : SGR_BACK;
    if (color & FLAG_BRIGHT) {
        // Some terminals don't support the 9X/10X "intensive" color parameters
        // (e.g. the Eclipse TM terminal as of this writing).  Those terminals
        // will quietly ignore a 9X/10X code, and the other terminals will
        // ignore a 3X/4X code if it's followed by a 9X/10X code.  Therefore,
        // output a 3X/4X code as a fallback, then override it.
        const int colorBase = color & ~FLAG_BRIGHT;
        outUInt(out, sgrBase + colorBase);
        out.push_back(';');
        outUInt(out, sgrBase + (SGR_FORE_HI - SGR_FORE) + colorBase);
    } else {
        outUInt(out, sgrBase + color);
    }
}

static void outputSetColor(std::string &out, int color)
{
    int fore = 0;
    int back = 0;
    if (color & FOREGROUND_RED)       fore |= FLAG_RED;
    if (color & FOREGROUND_GREEN)     fore |= FLAG_GREEN;
    if (color & FOREGROUND_BLUE)      fore |= FLAG_BLUE;
    if (color & FOREGROUND_INTENSITY) fore |= FLAG_BRIGHT;
    if (color & BACKGROUND_RED)       back |= FLAG_RED;
    if (color & BACKGROUND_GREEN)     back |= FLAG_GREEN;
    if (color & BACKGROUND_BLUE)      back |= FLAG_BLUE;
    if (color & BACKGROUND_INTENSITY) back |= FLAG_BRIGHT;

    // Translate the fore/back colors into terminal escape codes using
    // a heuristic that works OK with common white-on-black or
    // black-on-white color schemes.  We don't know which color scheme
    // the terminal is using.  It is ugly to force white-on-black text
    // on a black-on-white terminal, and it's even ugly to force the
    // matching scheme.  It's probably relevant that the default
    // fore/back terminal colors frequently do not match any of the 16
    // palette colors.

    // Typical default terminal color schemes (according to palette,
    // when possible):
    //  - mintty:               LtGray-on-Black(A)
    //  - putty:                LtGray-on-Black(A)
    //  - xterm:                LtGray-on-Black(A)
    //  - Konsole:              LtGray-on-Black(A)
    //  - JediTerm/JetBrains:   Black-on-White(B)
    //  - rxvt:                 Black-on-White(B)

    // If the background is the default color (black), then it will
    // map to Black(A) or White(B).  If we translate White to White,
    // then a Black background and a White background in the console
    // are both White with (B).  Therefore, we should translate White
    // using SGR 7 (Invert).  The typical finished mapping table for
    // background grayscale colors is:
    //
    //  (A) White => LtGray(fore)
    //  (A) Black => Black(back)
    //  (A) LtGray => LtGray
    //  (A) DkGray => DkGray
    //
    //  (B) White => Black(fore)
    //  (B) Black => White(back)
    //  (B) LtGray => LtGray
    //  (B) DkGray => DkGray
    //

    out.append(CSI"0");
    if (back == BLACK) {
        if (fore == LTGRAY) {
            // The "default" foreground color.  Use the terminal's
            // default colors.
        } else if (fore == WHITE) {
            // Sending the literal color white would behave poorly if
            // the terminal were black-on-white.  Sending Bold is not
            // guaranteed to alter the color, but it will make the text
            // visually distinct, so do that instead.
            out.append(";1");
        } else if (fore == DKGRAY) {
            // Set the foreground color to DkGray(90) with a fallback
            // of LtGray(37) for terminals that don't handle the 9X SGR
            // parameters (e.g. Eclipse's TM Terminal as of this
            // writing).
            out.append(";37;90");
        } else {
            outputSetColorSgrParams(out, true, fore);
        }
    } else if (back == WHITE) {
        // Set the background color using Invert on the default
        // foreground color, and set the foreground color by setting a
        // background color.

        // Use the terminal's inverted colors.
        out.append(";7");
        if (fore == LTGRAY || fore == BLACK) {
            // We're likely mapping Console White to terminal LtGray or
            // Black.  If they are the Console foreground color, then
            // don't set a terminal foreground color to avoid creating
            // invisible text.
        } else {
            outputSetColorSgrParams(out, false, fore);
        }
    } else {
        // Set the foreground and background to match exactly that in
        // the Windows console.
        outputSetColorSgrParams(out, true, fore);
        outputSetColorSgrParams(out, false, back);
    }
    if (fore == back) {
        // The foreground and background colors are exactly equal, so
        // attempt to hide the text using the Conceal SGR parameter,
        // which some terminals support.
        out.append(";8");
    }
    out.push_back('m');
}

// The Windows Console has a popup window (e.g. that appears with F7)
// that is sometimes bordered with box-drawing characters.  With the
// Japanese and Korean system locales (CP932 and CP949), the
// UnicodeChar values for the box-drawing characters are 1 through 6.
// Detect this and map the values to the correct Unicode values.
//
// N.B. In the English locale, the UnicodeChar values are correct, and
// they identify single-line characters rather than double-line.  In
// the Chinese Simplified and Traditional locales, the popups use ASCII
// characters instead.
static inline wchar_t fixConsolePopupBoxArt(wchar_t ch)
{
    if (ch <= 6) {
        switch (ch) {
            case 1: return 0x2554; // BOX DRAWINGS DOUBLE DOWN AND RIGHT
            case 2: return 0x2557; // BOX DRAWINGS DOUBLE DOWN AND LEFT
            case 3: return 0x255A; // BOX DRAWINGS DOUBLE UP AND RIGHT
            case 4: return 0x255D; // BOX DRAWINGS DOUBLE UP AND LEFT
            case 5: return 0x2551; // BOX DRAWINGS DOUBLE VERTICAL
            case 6: return 0x2550; // BOX DRAWINGS DOUBLE HORIZONTAL
        }
    }
    return ch;
}

} // anonymous namespace

Terminal::Terminal(NamedPipe *output) :
    m_output(output),
    m_remoteLine(0),
    m_cursorHidden(false),
    m_remoteColor(-1),
    m_consoleMode(false)
{
}

void Terminal::setConsoleMode(int mode)
{
    if (mode == 1)
        m_consoleMode = true;
    else
        m_consoleMode = false;
}

void Terminal::reset(bool sendClearFirst, int newLine)
{
    if (sendClearFirst && !m_consoleMode) {
        // 0m   ==> reset SGR parameters
        // 1;1H ==> move cursor to top-left position
        // 2J   ==> clear the entire screen
        m_output->write(CSI"0m" CSI"1;1H" CSI"2J");
    }
    m_remoteLine = newLine;
    m_cursorHidden = false;
    m_cursorPos = std::pair<int, int>(0, newLine);
    m_remoteColor = -1;
}

void Terminal::sendLine(int line, CHAR_INFO *lineData, int width)
{
    hideTerminalCursor();
    moveTerminalToLine(line);

    // Erase in Line -- erase entire line.
    if (!m_consoleMode)
        m_output->write(CSI"2K");

    m_termLine.clear();

    int length = 0;
    for (int i = 0; i < width; ++i) {
        int color = lineData[i].Attributes & COLOR_ATTRIBUTE_MASK;
        if (color != m_remoteColor && !m_consoleMode) {
            outputSetColor(m_termLine, color);
            length = m_termLine.size();
        }
        m_remoteColor = color;
        if (lineData[i].Attributes & WINPTY_COMMON_LVB_TRAILING_BYTE) {
            // CJK full-width characters occupy two console cells.  The first
            // cell is marked with COMMON_LVB_LEADING_BYTE, and the second is
            // marked with COMMON_LVB_TRAILING_BYTE.  Skip the trailing cells.
            continue;
        }
        // TODO: Is it inefficient to call WideCharToMultiByte once per
        // character?
        wchar_t ch = fixConsolePopupBoxArt(lineData[i].Char.UnicodeChar);
        char mbstr[16];
        int mblen = WideCharToMultiByte(CP_UTF8,
                                        0,
                                        &ch,
                                        1,
                                        mbstr,
                                        sizeof(mbstr),
                                        NULL,
                                        NULL);
        if (mblen <= 0) {
            mbstr[0] = '?';
            mblen = 1;
        }
        if (mblen == 1 && mbstr[0] == ' ') {
            m_termLine.push_back(' ');
        } else {
            m_termLine.append(mbstr, mblen);
            length = m_termLine.size();
        }
    }

    m_output->write(m_termLine.data(), length);
}

void Terminal::finishOutput(const std::pair<int, int> &newCursorPos)
{
    if (newCursorPos != m_cursorPos)
        hideTerminalCursor();
    if (m_cursorHidden) {
        moveTerminalToLine(newCursorPos.second);
        char buffer[32];
        sprintf(buffer, CSI"%dG" CSI"?25h", newCursorPos.first + 1);
        if (!m_consoleMode)
            m_output->write(buffer);
        m_cursorHidden = false;
    }
    m_cursorPos = newCursorPos;
}

void Terminal::hideTerminalCursor()
{
    if (m_cursorHidden)
        return;
    if (!m_consoleMode)
        m_output->write(CSI"?25l");
    m_cursorHidden = true;
}

void Terminal::moveTerminalToLine(int line)
{
    // Do not use CPL or CNL.  Konsole 2.5.4 does not support Cursor Previous
    // Line (CPL) -- there are "Undecodable sequence" errors.  gnome-terminal
    // 2.32.0 does handle it.  Cursor Next Line (CNL) does nothing if the
    // cursor is on the last line already.

    if (line < m_remoteLine) {
        // CUrsor Up (CUU)
        char buffer[32];
        sprintf(buffer, "\r" CSI"%dA", m_remoteLine - line);
        if (!m_consoleMode)
            m_output->write(buffer);
        m_remoteLine = line;
    } else if (line > m_remoteLine) {
        while (line > m_remoteLine) {
            if (!m_consoleMode)
                m_output->write("\r\n");
            m_remoteLine++;
        }
    } else {
        m_output->write("\r");
    }
}
<|endoftext|>