<commit_before>// KMail startup and initialize code
// Author: Stefan Taferner <taferner@alpin.or.at>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <kuniqueapplication.h>
#include <klocale.h>
#include <kglobal.h>
#include <ksimpleconfig.h>
#include <kstandarddirs.h>
#include <knotifyclient.h>
#include <dcopclient.h>
#include <kcrash.h>

#include "kmkernel.h" //control center

#undef Status // stupid X headers
#include "kmailIface_stub.h" // to call control center of master kmail

#include <kaboutdata.h>

#include "kmversion.h"

// OLD about text.  This is horrbly outdated.
/*const char* aboutText =
    "KMail [" KMAIL_VERSION "] by\n\n"
    "Stefan Taferner <taferner@kde.org>,\n"
    "Markus Wbben <markus.wuebben@kde.org>\n\n"
    "based on the work of:\n"
    "Lynx <lynx@topaz.hknet.com>,\n"
    "Stephan Meyer <Stephan.Meyer@pobox.com>,\n"
    "and the above authors.\n\n"
    "This program is covered by the GPL.\n\n"
    "Please send bugreports to taferner@kde.org";
*/

static KCmdLineOptions kmoptions[] =
{
  { "s", 0 , 0 },
  { "subject <subject>",	I18N_NOOP("Set subject of msg."), 0 },
  { "c", 0 , 0 },
  { "cc <address>",		I18N_NOOP("Send CC: to 'address'."), 0 },
  { "b", 0 , 0 },
  { "bcc <address>",		I18N_NOOP("Send BCC: to 'addres'."), 0 },
  { "h", 0 , 0 },
  { "header <header>",		I18N_NOOP("Add 'header' to msg."), 0 },
  { "msg <file>",		I18N_NOOP("Read msg-body from 'file'."), 0 },
  { "body <text>",              I18N_NOOP("Set body of msg."), 0 },
  { "attach <url>",             I18N_NOOP("Add an attachment to the mail, this can be repeated"), 0 },
  { "check",			I18N_NOOP("Check for new mail only."), 0 },
  { "composer",			I18N_NOOP("Open only composer window."), 0 },
  { "+[address]",		I18N_NOOP("Send msg to 'address'."), 0 },
//  { "+[file]",                  I18N_NOOP("Show message from file 'file'."), 0 },
  { 0, 0, 0}
};

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

extern "C" {

static void setSignalHandler(void (*handler)(int));

// Crash recovery signal handler
static void signalHandler(int sigId)
{
  setSignalHandler(SIG_DFL);
  fprintf(stderr, "*** KMail got signal %d (Exiting)\n", sigId);
  // try to cleanup all windows
  kernel->dumpDeadLetters();
  ::exit(-1); //
}

// Crash recovery signal handler
static void crashHandler(int sigId)
{
  setSignalHandler(SIG_DFL);
  fprintf(stderr, "*** KMail got signal %d (Crashing)\n", sigId);
  // try to cleanup all windows
  kernel->dumpDeadLetters();
  // Return to DrKonqi.
}
//-----------------------------------------------------------------------------

static void setSignalHandler(void (*handler)(int))
{
  signal(SIGKILL, handler);
  signal(SIGTERM, handler);
  signal(SIGHUP,  handler);
  KCrash::setEmergencySaveFunction(crashHandler);
}

}
//-----------------------------------------------------------------------------

class KMailApplication : public KUniqueApplication
{
public:
  KMailApplication() : KUniqueApplication() { };

  virtual int newInstance();

  void commitData(QSessionManager& sm) {
    kernel->notClosedByUser();
    KApplication::commitData( sm );
  }
};

int KMailApplication::newInstance()
{
  QString to, cc, bcc, subj, body;
  KURL messageFile = QString::null;
  KURL::List attachURLs;
  bool mailto = false;
  bool checkMail = false;
  //bool viewOnly = false;

  // process args:
  KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
  if (args->getOption("subject"))
  {
     mailto = true;
     subj = QString::fromLocal8Bit(args->getOption("subject"));
  }

  if (args->getOption("cc"))
  {
     mailto = true;
     cc = QString::fromLocal8Bit(args->getOption("cc"));
  }

  if (args->getOption("bcc"))
  {
     mailto = true;
     bcc = QString::fromLocal8Bit(args->getOption("bcc"));
  }

  if (args->getOption("msg"))
  {
     mailto = true;
     messageFile = QString::fromLocal8Bit(args->getOption("msg"));
  }

  if (args->getOption("body"))
  {
     mailto = true;
     body = QString::fromLocal8Bit(args->getOption("body"));
  }

  QCStringList attachList = args->getOptionList("attach");
  if (!attachList.isEmpty())
  {
     mailto = true;
     for ( QCStringList::Iterator it = attachList.begin() ; it != attachList.end() ; ++it )
         attachURLs += KURL( QString::fromLocal8Bit( *it ) );
  }

  if (args->isSet("composer"))
    mailto = true;

  if (args->isSet("check"))
    checkMail = true;

  for(int i= 0; i < args->count(); i++)
  {
    if (!to.isEmpty())
      to += ", ";
     if (strncasecmp(args->arg(i),"mailto:",7)==0)
       to += args->arg(i);
     else
       to += args->arg(i);
     mailto = true;
  }

  args->clear();

  if (!kapp->isRestored())
    kernel->action (mailto, checkMail, to, cc, bcc, subj, body, messageFile, attachURLs);
  return 0;
}

int main(int argc, char *argv[])
{
  // WABA: KMail is a KUniqueApplication. Unfortunately this makes debugging
  // a bit harder: You should pass --nofork as commandline argument when using
  // a debugger. In gdb you can do this by typing "set args --nofork" before
  // typing "run".

  KAboutData about("kmail", I18N_NOOP("KMail"),
                   KMAIL_VERSION,
		   I18N_NOOP("The KDE Email client."),
		   KAboutData::License_GPL,
                   I18N_NOOP("(c) 1997-2001, The KMail developers"),
		   0,
		   "http://kmail.kde.org");
  about.addAuthor( "Michael H\303\244ckel", I18N_NOOP("Current maintainer"), "michael@haeckel.net" );
  about.addAuthor( "Don Sanders", I18N_NOOP("Core developer and former maintainer"), "sanders@kde.org" );
  about.addAuthor( "Stefan Taferner ", I18N_NOOP("Original author"), "taferner@kde.org" );
  about.addAuthor( "Ingo Kl\303\266cker", I18N_NOOP("Encryption"), "ingo.kloecker@epost.de" );
  about.addAuthor( "Marc Mutz", I18N_NOOP("Core developer"), "mutz@kde.org" );
  about.addAuthor( "Daniel Naber", I18N_NOOP("Documentation"), "daniel.naber@t-online.de" );
  about.addAuthor( "Andreas Gungl", I18N_NOOP("Encryption"), "a.gungl@gmx.de" );

  about.addAuthor( "Toyohiro Asukai", 0, "toyohiro@ksmplus.com" );
  about.addAuthor( "Waldo Bastian", 0, "bastian@kde.org" );
  about.addAuthor( "Carsten Burghardt", 0, "carsten.burghardt@web.de" );
  about.addAuthor( "Steven Brown", 0, "swbrown@ucsd.edu" );
  about.addAuthor( "Cristi Dumitrescu", 0, "cristid@chip.ro" );
  about.addAuthor( "Philippe Fremy", 0, "pfremy@chez.com" );
  about.addAuthor( "Kurt Granroth", 0, "granroth@kde.org" );
  about.addAuthor( "Heiko Hund", 0, "heiko@ist.eigentlich.net" );
  about.addAuthor( "Igor Janssen", 0, "rm@linux.ru.net" );
  about.addAuthor( "Matt Johnston", 0, "matt@caifex.org" );
  about.addAuthor( "Christer Kaivo-oja", 0, "whizkid@telia.com" );
  about.addAuthor( "Lars Knoll", 0, "knoll@kde.org" );
  about.addAuthor( "J. Nick Koston", 0, "bdraco@darkorb.net" );
  about.addAuthor( "Stephan Kulow", 0, "coolo@kde.org" );
  about.addAuthor( "Guillaume Laurent", 0, "glaurent@telegraph-road.org" );
  about.addAuthor( "Sam Magnuson", 0, "sam@trolltech.com" );
  about.addAuthor( "Laurent Montel", 0, "lmontel@mandrakesoft.com" );
  about.addAuthor( "Matt Newell", 0, "newellm@proaxis.com" );
  about.addAuthor( "Denis Perchine", 0, "dyp@perchine.com" );
  about.addAuthor( "Samuel Penn", 0, "sam@bifrost.demon.co.uk" );
  about.addAuthor( "Carsten Pfeiffer", 0, "pfeiffer@kde.org" );
  about.addAuthor( "Sven Radej", 0, "radej@kde.org" );
  about.addAuthor( "Mark Roberts", 0, "mark@taurine.demon.co.uk" );
  about.addAuthor( "Wolfgang Rohdewald", 0, "wrohdewald@dplanet.ch" );
  about.addAuthor( "Espen Sand", 0, "espen@kde.org" );
  about.addAuthor( "George Staikos", 0, "staikos@kde.org" );
  about.addAuthor( "Jason Stephenson", 0, "panda@mis.net" );
  about.addAuthor( "Jacek Stolarczyk", 0, "jacek@mer.chemia.polsl.gliwice.pl" );
  about.addAuthor( "Roberto S. Teixeira", 0, "maragato@kde.org" );
  about.addAuthor( "Ronen Tzur", 0, "rtzur@shani.net" );
  about.addAuthor( "Mario Weilguni", 0, "mweilguni@sime.com" );
  about.addAuthor( "Wynn Wilkes", 0, "wynnw@calderasystems.com" );
  about.addAuthor( "Robert D. Williams", 0, "rwilliams@kde.org" );
  about.addAuthor( "Markus Wuebben", 0, "markus.wuebben@kde.org" );
  about.addAuthor( "Thorsten Zachmann", 0, "t.zachmann@zagge.de" );
  about.addAuthor( "Karl-Heinz Zimmer", 0, "khz@kde.org" );

  KCmdLineArgs::init(argc, argv, &about);
  KCmdLineArgs::addCmdLineOptions( kmoptions ); // Add kmail options

  if (!KMailApplication::start())
     return 0;

  KMailApplication app;
  KGlobal::locale()->insertCatalogue("libkdenetwork");

  KSimpleConfig config(locateLocal("appdata", "lock"));
  int oldPid = config.readNumEntry("pid", -1);
  if (oldPid != -1 && kill(oldPid, 0) != -1)
  {
    QString msg = i18n("Only one instance of KMail can be run at "
      "any one time. It is already running on a different display "
      "with PID %1.").arg(oldPid);

    KNotifyClient::userEvent( msg,  KNotifyClient::Messagebox,
      KNotifyClient::Error );
    fprintf(stderr, "*** KMail is already running with PID %d\n", oldPid);
    return 1;
  }

  config.writeEntry("pid", getpid());
  config.sync();

  kapp->dcopClient()->suspend(); // Don't handle DCOP requests yet

  //local, do the init
  KMKernel kmailKernel;
  kmailKernel.init();
  kapp->dcopClient()->setDefaultObject( kmailKernel.objId() );

  // and session management
  kmailKernel.doSessionManagement();

  // any dead letters?
  kmailKernel.recoverDeadLetters();

  setSignalHandler(signalHandler);

  kapp->dcopClient()->resume(); // Ok. We are ready for DCOP requests.
  // Go!
  int ret = kapp->exec();

  // clean up
  kmailKernel.cleanup();
  config.writeEntry("pid", -1);
  config.sync();
  return ret;
}

<commit_msg>Fixed "malformed URL" error message when clicking on a mailto: link.<commit_after>// KMail startup and initialize code
// Author: Stefan Taferner <taferner@alpin.or.at>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <kuniqueapplication.h>
#include <klocale.h>
#include <kglobal.h>
#include <ksimpleconfig.h>
#include <kstandarddirs.h>
#include <knotifyclient.h>
#include <dcopclient.h>
#include <kcrash.h>

#include "kmkernel.h" //control center

#undef Status // stupid X headers
#include "kmailIface_stub.h" // to call control center of master kmail

#include <kaboutdata.h>

#include "kmversion.h"

// OLD about text.  This is horrbly outdated.
/*const char* aboutText =
    "KMail [" KMAIL_VERSION "] by\n\n"
    "Stefan Taferner <taferner@kde.org>,\n"
    "Markus Wbben <markus.wuebben@kde.org>\n\n"
    "based on the work of:\n"
    "Lynx <lynx@topaz.hknet.com>,\n"
    "Stephan Meyer <Stephan.Meyer@pobox.com>,\n"
    "and the above authors.\n\n"
    "This program is covered by the GPL.\n\n"
    "Please send bugreports to taferner@kde.org";
*/

static KCmdLineOptions kmoptions[] =
{
  { "s", 0 , 0 },
  { "subject <subject>",	I18N_NOOP("Set subject of msg."), 0 },
  { "c", 0 , 0 },
  { "cc <address>",		I18N_NOOP("Send CC: to 'address'."), 0 },
  { "b", 0 , 0 },
  { "bcc <address>",		I18N_NOOP("Send BCC: to 'addres'."), 0 },
  { "h", 0 , 0 },
  { "header <header>",		I18N_NOOP("Add 'header' to msg."), 0 },
  { "msg <file>",		I18N_NOOP("Read msg-body from 'file'."), 0 },
  { "body <text>",              I18N_NOOP("Set body of msg."), 0 },
  { "attach <url>",             I18N_NOOP("Add an attachment to the mail, this can be repeated"), 0 },
  { "check",			I18N_NOOP("Check for new mail only."), 0 },
  { "composer",			I18N_NOOP("Open only composer window."), 0 },
  { "+[address]",		I18N_NOOP("Send msg to 'address'."), 0 },
//  { "+[file]",                  I18N_NOOP("Show message from file 'file'."), 0 },
  { 0, 0, 0}
};

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

extern "C" {

static void setSignalHandler(void (*handler)(int));

// Crash recovery signal handler
static void signalHandler(int sigId)
{
  setSignalHandler(SIG_DFL);
  fprintf(stderr, "*** KMail got signal %d (Exiting)\n", sigId);
  // try to cleanup all windows
  kernel->dumpDeadLetters();
  ::exit(-1); //
}

// Crash recovery signal handler
static void crashHandler(int sigId)
{
  setSignalHandler(SIG_DFL);
  fprintf(stderr, "*** KMail got signal %d (Crashing)\n", sigId);
  // try to cleanup all windows
  kernel->dumpDeadLetters();
  // Return to DrKonqi.
}
//-----------------------------------------------------------------------------

static void setSignalHandler(void (*handler)(int))
{
  signal(SIGKILL, handler);
  signal(SIGTERM, handler);
  signal(SIGHUP,  handler);
  KCrash::setEmergencySaveFunction(crashHandler);
}

}
//-----------------------------------------------------------------------------

class KMailApplication : public KUniqueApplication
{
public:
  KMailApplication() : KUniqueApplication() { };

  virtual int newInstance();

  void commitData(QSessionManager& sm) {
    kernel->notClosedByUser();
    KApplication::commitData( sm );
  }
};

int KMailApplication::newInstance()
{
  QString to, cc, bcc, subj, body;
  KURL messageFile = QString::null;
  KURL::List attachURLs;
  bool mailto = false;
  bool checkMail = false;
  //bool viewOnly = false;

  // process args:
  KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
  if (args->getOption("subject"))
  {
     mailto = true;
     subj = QString::fromLocal8Bit(args->getOption("subject"));
  }

  if (args->getOption("cc"))
  {
     mailto = true;
     cc = QString::fromLocal8Bit(args->getOption("cc"));
  }

  if (args->getOption("bcc"))
  {
     mailto = true;
     bcc = QString::fromLocal8Bit(args->getOption("bcc"));
  }

  if (args->getOption("msg"))
  {
     mailto = true;
     messageFile = QString::fromLocal8Bit(args->getOption("msg"));
  }

  if (args->getOption("body"))
  {
     mailto = true;
     body = QString::fromLocal8Bit(args->getOption("body"));
  }

  QCStringList attachList = args->getOptionList("attach");
  if (!attachList.isEmpty())
  {
     mailto = true;
     for ( QCStringList::Iterator it = attachList.begin() ; it != attachList.end() ; ++it )
       if ( !(*it).isEmpty() )
         attachURLs += KURL( QString::fromLocal8Bit( *it ) );
  }

  if (args->isSet("composer"))
    mailto = true;

  if (args->isSet("check"))
    checkMail = true;

  for(int i= 0; i < args->count(); i++)
  {
    if (!to.isEmpty())
      to += ", ";
     if (strncasecmp(args->arg(i),"mailto:",7)==0)
       to += args->arg(i);
     else
       to += args->arg(i);
     mailto = true;
  }

  args->clear();

  if (!kapp->isRestored())
    kernel->action (mailto, checkMail, to, cc, bcc, subj, body, messageFile, attachURLs);
  return 0;
}

int main(int argc, char *argv[])
{
  // WABA: KMail is a KUniqueApplication. Unfortunately this makes debugging
  // a bit harder: You should pass --nofork as commandline argument when using
  // a debugger. In gdb you can do this by typing "set args --nofork" before
  // typing "run".

  KAboutData about("kmail", I18N_NOOP("KMail"),
                   KMAIL_VERSION,
		   I18N_NOOP("The KDE Email client."),
		   KAboutData::License_GPL,
                   I18N_NOOP("(c) 1997-2001, The KMail developers"),
		   0,
		   "http://kmail.kde.org");
  about.addAuthor( "Michael H\303\244ckel", I18N_NOOP("Current maintainer"), "michael@haeckel.net" );
  about.addAuthor( "Don Sanders", I18N_NOOP("Core developer and former maintainer"), "sanders@kde.org" );
  about.addAuthor( "Stefan Taferner ", I18N_NOOP("Original author"), "taferner@kde.org" );
  about.addAuthor( "Ingo Kl\303\266cker", I18N_NOOP("Encryption"), "ingo.kloecker@epost.de" );
  about.addAuthor( "Marc Mutz", I18N_NOOP("Core developer"), "mutz@kde.org" );
  about.addAuthor( "Daniel Naber", I18N_NOOP("Documentation"), "daniel.naber@t-online.de" );
  about.addAuthor( "Andreas Gungl", I18N_NOOP("Encryption"), "a.gungl@gmx.de" );

  about.addAuthor( "Toyohiro Asukai", 0, "toyohiro@ksmplus.com" );
  about.addAuthor( "Waldo Bastian", 0, "bastian@kde.org" );
  about.addAuthor( "Carsten Burghardt", 0, "carsten.burghardt@web.de" );
  about.addAuthor( "Steven Brown", 0, "swbrown@ucsd.edu" );
  about.addAuthor( "Cristi Dumitrescu", 0, "cristid@chip.ro" );
  about.addAuthor( "Philippe Fremy", 0, "pfremy@chez.com" );
  about.addAuthor( "Kurt Granroth", 0, "granroth@kde.org" );
  about.addAuthor( "Heiko Hund", 0, "heiko@ist.eigentlich.net" );
  about.addAuthor( "Igor Janssen", 0, "rm@linux.ru.net" );
  about.addAuthor( "Matt Johnston", 0, "matt@caifex.org" );
  about.addAuthor( "Christer Kaivo-oja", 0, "whizkid@telia.com" );
  about.addAuthor( "Lars Knoll", 0, "knoll@kde.org" );
  about.addAuthor( "J. Nick Koston", 0, "bdraco@darkorb.net" );
  about.addAuthor( "Stephan Kulow", 0, "coolo@kde.org" );
  about.addAuthor( "Guillaume Laurent", 0, "glaurent@telegraph-road.org" );
  about.addAuthor( "Sam Magnuson", 0, "sam@trolltech.com" );
  about.addAuthor( "Laurent Montel", 0, "lmontel@mandrakesoft.com" );
  about.addAuthor( "Matt Newell", 0, "newellm@proaxis.com" );
  about.addAuthor( "Denis Perchine", 0, "dyp@perchine.com" );
  about.addAuthor( "Samuel Penn", 0, "sam@bifrost.demon.co.uk" );
  about.addAuthor( "Carsten Pfeiffer", 0, "pfeiffer@kde.org" );
  about.addAuthor( "Sven Radej", 0, "radej@kde.org" );
  about.addAuthor( "Mark Roberts", 0, "mark@taurine.demon.co.uk" );
  about.addAuthor( "Wolfgang Rohdewald", 0, "wrohdewald@dplanet.ch" );
  about.addAuthor( "Espen Sand", 0, "espen@kde.org" );
  about.addAuthor( "George Staikos", 0, "staikos@kde.org" );
  about.addAuthor( "Jason Stephenson", 0, "panda@mis.net" );
  about.addAuthor( "Jacek Stolarczyk", 0, "jacek@mer.chemia.polsl.gliwice.pl" );
  about.addAuthor( "Roberto S. Teixeira", 0, "maragato@kde.org" );
  about.addAuthor( "Ronen Tzur", 0, "rtzur@shani.net" );
  about.addAuthor( "Mario Weilguni", 0, "mweilguni@sime.com" );
  about.addAuthor( "Wynn Wilkes", 0, "wynnw@calderasystems.com" );
  about.addAuthor( "Robert D. Williams", 0, "rwilliams@kde.org" );
  about.addAuthor( "Markus Wuebben", 0, "markus.wuebben@kde.org" );
  about.addAuthor( "Thorsten Zachmann", 0, "t.zachmann@zagge.de" );
  about.addAuthor( "Karl-Heinz Zimmer", 0, "khz@kde.org" );

  KCmdLineArgs::init(argc, argv, &about);
  KCmdLineArgs::addCmdLineOptions( kmoptions ); // Add kmail options

  if (!KMailApplication::start())
     return 0;

  KMailApplication app;
  KGlobal::locale()->insertCatalogue("libkdenetwork");

  KSimpleConfig config(locateLocal("appdata", "lock"));
  int oldPid = config.readNumEntry("pid", -1);
  if (oldPid != -1 && kill(oldPid, 0) != -1)
  {
    QString msg = i18n("Only one instance of KMail can be run at "
      "any one time. It is already running on a different display "
      "with PID %1.").arg(oldPid);

    KNotifyClient::userEvent( msg,  KNotifyClient::Messagebox,
      KNotifyClient::Error );
    fprintf(stderr, "*** KMail is already running with PID %d\n", oldPid);
    return 1;
  }

  config.writeEntry("pid", getpid());
  config.sync();

  kapp->dcopClient()->suspend(); // Don't handle DCOP requests yet

  //local, do the init
  KMKernel kmailKernel;
  kmailKernel.init();
  kapp->dcopClient()->setDefaultObject( kmailKernel.objId() );

  // and session management
  kmailKernel.doSessionManagement();

  // any dead letters?
  kmailKernel.recoverDeadLetters();

  setSignalHandler(signalHandler);

  kapp->dcopClient()->resume(); // Ok. We are ready for DCOP requests.
  // Go!
  int ret = kapp->exec();

  // clean up
  kmailKernel.cleanup();
  config.writeEntry("pid", -1);
  config.sync();
  return ret;
}

<|endoftext|>


<commit_before>/*     Copyright 2015-2018 Egor Yusov
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  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 OF ANY PROPRIETARY RIGHTS.
 *
 *  In no event and under no legal theory, whether in tort (including negligence),
 *  contract, or otherwise, unless required by applicable law (such as deliberate
 *  and grossly negligent acts) or agreed to in writing, shall any Contributor be
 *  liable for any damages, including any direct, indirect, special, incidental,
 *  or consequential damages of any character arising as a result of this License or
 *  out of the use or inability to use the software (including but not limited to damages
 *  for loss of goodwill, work stoppage, computer failure or malfunction, or any and
 *  all other commercial damages or losses), even if such Contributor has been advised
 *  of the possibility of such damages.
 */

#include "pch.h"

#include "GLContextAndroid.h"
#include "EngineGLAttribs.h"

#ifndef EGL_CONTEXT_MINOR_VERSION_KHR
#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB
#endif

#ifndef EGL_CONTEXT_MAJOR_VERSION_KHR
#define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
#endif

namespace Diligent
{
    bool GLContext::InitEGLSurface()
    {
        display_ = eglGetDisplay( EGL_DEFAULT_DISPLAY );
        if( display_ == EGL_NO_DISPLAY )
        {
            LOG_ERROR_AND_THROW( "No EGL display found" );
        }

        auto success = eglInitialize( display_, 0, 0 );
        if( !success )
        {
            LOG_ERROR_AND_THROW( "Failed to initialise EGL" );
        }

        /*
        * Here specify the attributes of the desired configuration.
        * Below, we select an EGLConfig with at least 8 bits per color
        * component compatible with on-screen windows
        */
        color_size_ = 8;
        depth_size_ = 24;
        const EGLint attribs[] =
        {
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //Request opengl ES2.0
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            //EGL_COLORSPACE, EGL_COLORSPACE_sRGB, // does not work
            EGL_BLUE_SIZE,  color_size_,
            EGL_GREEN_SIZE, color_size_,
            EGL_RED_SIZE,   color_size_,
            EGL_ALPHA_SIZE, color_size_,
            EGL_DEPTH_SIZE, depth_size_,
            //EGL_SAMPLE_BUFFERS  , 1,
            //EGL_SAMPLES         , 4,
            EGL_NONE
        };

        // Get a list of EGL frame buffer configurations that match specified attributes
        EGLint num_configs;
        success = eglChooseConfig( display_, attribs, &config_, 1, &num_configs );
        if( !success )
        {
            LOG_ERROR_AND_THROW( "Failed to choose config" );
        }

        if( !num_configs )
        {
            //Fall back to 16bit depth buffer
            depth_size_ = 16;
            const EGLint attribs[] =
            {
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //Request opengl ES2.0
                EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                EGL_BLUE_SIZE,  color_size_,
                EGL_GREEN_SIZE, color_size_,
                EGL_RED_SIZE,   color_size_,
                EGL_ALPHA_SIZE, color_size_,
                EGL_DEPTH_SIZE, depth_size_,
                EGL_NONE
            };
            success = eglChooseConfig( display_, attribs, &config_, 1, &num_configs );
            if( !success )
            {
                LOG_ERROR_AND_THROW( "Failed to choose 16-bit depth config" );
            }
        }

        if( !num_configs )
        {
            LOG_ERROR_AND_THROW( "Unable to retrieve EGL config" );
        }

        LOG_INFO_MESSAGE("Chosen EGL config: ", color_size_, " bit color, ", depth_size_, " bit depth");

        surface_ = eglCreateWindowSurface( display_, config_, window_, NULL );
        if( surface_ == EGL_NO_SURFACE )
        {
            LOG_ERROR_AND_THROW( "Failed to create EGLSurface" );
        }

        eglQuerySurface( display_, surface_, EGL_WIDTH, &screen_width_ );
        eglQuerySurface( display_, surface_, EGL_HEIGHT, &screen_height_ );

        /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
        * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
        * As soon as we picked a EGLConfig, we can safely reconfigure the
        * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
        EGLint format;
        eglGetConfigAttrib( display_, config_, EGL_NATIVE_VISUAL_ID, &format );
        ANativeWindow_setBuffersGeometry( window_, 0, 0, format );

        return true;
    }

    bool GLContext::InitEGLContext()
    {
        major_version_ = 3;
        minor_version_ = 1;

        const EGLint context_attribs[] =
        {
            EGL_CONTEXT_CLIENT_VERSION, major_version_,
            EGL_CONTEXT_MINOR_VERSION_KHR, minor_version_,
            EGL_NONE
        };

        LOG_INFO_MESSAGE( "contextAttribs: ", context_attribs[0], ' ', context_attribs[1], '\n' );
        LOG_INFO_MESSAGE( "contextAttribs: ", context_attribs[2], ' ', context_attribs[3], '\n' );

        context_ = eglCreateContext( display_, config_, NULL, context_attribs );
        if( context_ == EGL_NO_CONTEXT )
        {
            LOG_ERROR_AND_THROW( "Failed to create EGLContext" );
        }

        if( eglMakeCurrent( display_, surface_, surface_, context_ ) == EGL_FALSE )
        {
            LOG_ERROR_AND_THROW( "Unable to eglMakeCurrent" );
        }

        context_valid_ = true;
        return true;
    }

    void GLContext::AttachToCurrentEGLContext()
    {
        if( eglGetCurrentContext() == EGL_NO_CONTEXT )
        {
            LOG_ERROR_AND_THROW( "Failed to attach to EGLContext: no active context" );
        }
        context_valid_ = true;
        glGetIntegerv( GL_MAJOR_VERSION, &major_version_ );
        glGetIntegerv( GL_MINOR_VERSION, &minor_version_ );
    }

    GLContext::NativeGLContextType GLContext::GetCurrentNativeGLContext()
    {
        return eglGetCurrentContext();
    }

    void GLContext::InitGLES()
    {
        if( gles_initialized_ )
            return;
        //
        //Initialize OpenGL ES 3 if available
        //
        const char* versionStr = (const char*)glGetString( GL_VERSION );
        LOG_INFO_MESSAGE( "GL Version: ", versionStr, '\n' );

        LoadGLFunctions();

        // When GL_FRAMEBUFFER_SRGB is enabled, and if the destination image is in the sRGB colorspace
        // then OpenGL will assume the shader's output is in the linear RGB colorspace. It will therefore
        // convert the output from linear RGB to sRGB.
        // Any writes to images that are not in the sRGB format should not be affected.
        // Thus this setting should be just set once and left that way
        glEnable(GL_FRAMEBUFFER_SRGB);
        if( glGetError() != GL_NO_ERROR )
            LOG_ERROR_MESSAGE("Failed to enable SRGB framebuffers");

        gles_initialized_ = true;
    }

    bool GLContext::Init( ANativeWindow* window )
    {
        if( egl_context_initialized_ )
            return true;

        //
        //Initialize EGL
        //
        window_ = window;
        if (window != nullptr)
        {
            InitEGLSurface();
            InitEGLContext();
        }
        else
        {
            AttachToCurrentEGLContext();
        }
        InitGLES();

        egl_context_initialized_ = true;

        return true;
    }

    GLContext::GLContext( const EngineGLAttribs &InitAttribs, DeviceCaps &DeviceCaps ) :
        display_( EGL_NO_DISPLAY ),
        surface_( EGL_NO_SURFACE ),
        context_( EGL_NO_CONTEXT ),
        egl_context_initialized_( false ),
        gles_initialized_( false ),
        major_version_(0),
        minor_version_(0)
    {
        auto *NativeWindow = reinterpret_cast<ANativeWindow*>(InitAttribs.pNativeWndHandle);
        Init( NativeWindow );

        FillDeviceCaps(DeviceCaps);
#if 0
        // Creates table of supported extensions strings
        extensions.clear();
        string tmp;
        sint32 begin, end;
        tmp = string( (char*)glGetString( GL_EXTENSIONS ) );

        begin = 0;
        end = tmp.find( ' ', 0 );

        DEBUG_PRINT( _L( "Checking Extensions" ) );

        while( end != string::npos )
        {
            DEBUG_PRINT( (_L( "extension %s" )), tmp.substr( begin, end - begin ).c_str() );
            extensions.insert( extensions.end(), tmp.substr( begin, end - begin ) );
            begin = end + 1;
            end = tmp.find( ' ', begin );
        }

        if( supportExtension( "GL_INTEL_tessellation" ) )
        {
            glPatchParameteri = (PFNGLPATCHPARAMETERIPROC)eglGetProcAddress( "glPatchParameteri" );
            DEBUG_PRINT( _L( "%s = %p" ), "glPatchParameteri", (void*)glPatchParameteri );
            glPatchParameterfv = (PFNGLPATCHPARAMETERFVPROC)eglGetProcAddress( "glPatchParameterfv" );
            DEBUG_PRINT( _L( "%s = %p" ), "glPatchParameterfv", (void*)glPatchParameterfv );
        }
        //if(supportExtension("GL_INTEL_compute_shader"))
        {
            glDispatchCompute = (PFNGLDISPATCHCOMPUTEPROC)eglGetProcAddress( "glDispatchCompute" );
            DEBUG_PRINT( _L( "%s = %p" ), "glDispatchCompute", (void*)glDispatchCompute );
            glBindImageTexture = (PFNGLBINDIMAGETEXTUREPROC)eglGetProcAddress( "glBindImageTexture" );
            DEBUG_PRINT( _L( "%s = %p" ), "glBindImageTexture", (void*)glBindImageTexture );
        }
#endif
    }

    GLContext::~GLContext()
    {
        Terminate();
    }

    void GLContext::SwapBuffers()
    {
        bool b = eglSwapBuffers( display_, surface_ );
        if( !b )
        {
            EGLint err = eglGetError();
            if( err == EGL_BAD_SURFACE )
            {
                //Recreate surface
                InitEGLSurface();
                //return EGL_SUCCESS; //Still consider glContext is valid
            }
            else if( err == EGL_CONTEXT_LOST || err == EGL_BAD_CONTEXT )
            {
                //Context has been lost!!
                context_valid_ = false;
                Terminate();
                InitEGLContext();
            }
            //return err;
        }
        //return EGL_SUCCESS;
    }

    void GLContext::Terminate()
    {
        if( display_ != EGL_NO_DISPLAY )
        {
            eglMakeCurrent( display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
            if( context_ != EGL_NO_CONTEXT )
            {
                eglDestroyContext( display_, context_ );
            }

            if( surface_ != EGL_NO_SURFACE )
            {
                eglDestroySurface( display_, surface_ );
            }
            eglTerminate( display_ );
        }

        display_ = EGL_NO_DISPLAY;
        context_ = EGL_NO_CONTEXT;
        surface_ = EGL_NO_SURFACE;
        context_valid_ = false;
    }

    EGLint GLContext::Resume( ANativeWindow* window )
    {
        if( egl_context_initialized_ == false )
        {
            Init( window );
            return EGL_SUCCESS;
        }

        //Create surface
        window_ = window;
        surface_ = eglCreateWindowSurface( display_, config_, window_, NULL );
        int32_t new_screen_width  = 0;
        int32_t new_screen_height = 0;
        eglQuerySurface( display_, surface_, EGL_WIDTH, &new_screen_width );
        eglQuerySurface( display_, surface_, EGL_HEIGHT, &new_screen_height );

        if( new_screen_width != screen_width_ || new_screen_height != screen_height_ )
        {
            //Screen resized
            LOG_INFO_MESSAGE( "Screen resized\n" );
        }

        if( eglMakeCurrent( display_, surface_, surface_, context_ ) == EGL_TRUE )
            return EGL_SUCCESS;

        EGLint err = eglGetError();
        LOG_WARNING_MESSAGE( "Unable to eglMakeCurrent ", err, '\n' );

        if( err == EGL_CONTEXT_LOST )
        {
            //Recreate context
            LOG_INFO_MESSAGE( "Re-creating egl context\n" );
            InitEGLContext();
        }
        else
        {
            //Recreate surface
            Terminate();
            InitEGLSurface();
            InitEGLContext();
        }

        return err;

    }

    void GLContext::Suspend()
    {
        if( surface_ != EGL_NO_SURFACE )
        {
            eglDestroySurface( display_, surface_ );
            surface_ = EGL_NO_SURFACE;
        }
    }

    bool GLContext::Invalidate()
    {
        Terminate();

        egl_context_initialized_ = false;
        return true;
    }

    void GLContext::FillDeviceCaps( DeviceCaps &DeviceCaps )
    {
        DeviceCaps.DevType = DeviceType::OpenGLES;
        DeviceCaps.MajorVersion = major_version_;
        DeviceCaps.MinorVersion = minor_version_;
        bool IsGLES31OrAbove = (major_version_ >= 4 || (major_version_ == 3 && minor_version_ >= 1) );
        DeviceCaps.bSeparableProgramSupported      = IsGLES31OrAbove;
        DeviceCaps.bIndirectRenderingSupported     = IsGLES31OrAbove;

        auto &SamCaps = DeviceCaps.SamCaps;
        SamCaps.bBorderSamplingModeSupported   = GL_TEXTURE_BORDER_COLOR       && IsGLES31OrAbove;
        SamCaps.bAnisotropicFilteringSupported = GL_TEXTURE_MAX_ANISOTROPY_EXT && IsGLES31OrAbove;
        SamCaps.bLODBiasSupported              = GL_TEXTURE_LOD_BIAS           && IsGLES31OrAbove;

        auto &TexCaps = DeviceCaps.TexCaps;
        TexCaps.bTexture1DSupported            = False;     // Not supported in GLES 3.1
        TexCaps.bTexture1DArraySupported       = False;     // Not supported in GLES 3.1
        TexCaps.bTexture2DMSSupported          = IsGLES31OrAbove;
        TexCaps.bTexture2DMSArraySupported     = False;     // Not supported in GLES 3.1
        TexCaps.bTextureViewSupported          = False;     // Not supported in GLES 3.1
        TexCaps.bCubemapArraysSupported        = False;     // Not supported in GLES 3.1
        DeviceCaps.bMultithreadedResourceCreationSupported = False;
    }
}
<commit_msg>Fixed Android crash when SwapBuffers() is called after Suspend()<commit_after>/*     Copyright 2015-2018 Egor Yusov
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  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 OF ANY PROPRIETARY RIGHTS.
 *
 *  In no event and under no legal theory, whether in tort (including negligence),
 *  contract, or otherwise, unless required by applicable law (such as deliberate
 *  and grossly negligent acts) or agreed to in writing, shall any Contributor be
 *  liable for any damages, including any direct, indirect, special, incidental,
 *  or consequential damages of any character arising as a result of this License or
 *  out of the use or inability to use the software (including but not limited to damages
 *  for loss of goodwill, work stoppage, computer failure or malfunction, or any and
 *  all other commercial damages or losses), even if such Contributor has been advised
 *  of the possibility of such damages.
 */

#include "pch.h"

#include "GLContextAndroid.h"
#include "EngineGLAttribs.h"

#ifndef EGL_CONTEXT_MINOR_VERSION_KHR
#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB
#endif

#ifndef EGL_CONTEXT_MAJOR_VERSION_KHR
#define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
#endif

namespace Diligent
{
    bool GLContext::InitEGLSurface()
    {
        display_ = eglGetDisplay( EGL_DEFAULT_DISPLAY );
        if( display_ == EGL_NO_DISPLAY )
        {
            LOG_ERROR_AND_THROW( "No EGL display found" );
        }

        auto success = eglInitialize( display_, 0, 0 );
        if( !success )
        {
            LOG_ERROR_AND_THROW( "Failed to initialise EGL" );
        }

        /*
        * Here specify the attributes of the desired configuration.
        * Below, we select an EGLConfig with at least 8 bits per color
        * component compatible with on-screen windows
        */
        color_size_ = 8;
        depth_size_ = 24;
        const EGLint attribs[] =
        {
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //Request opengl ES2.0
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            //EGL_COLORSPACE, EGL_COLORSPACE_sRGB, // does not work
            EGL_BLUE_SIZE,  color_size_,
            EGL_GREEN_SIZE, color_size_,
            EGL_RED_SIZE,   color_size_,
            EGL_ALPHA_SIZE, color_size_,
            EGL_DEPTH_SIZE, depth_size_,
            //EGL_SAMPLE_BUFFERS  , 1,
            //EGL_SAMPLES         , 4,
            EGL_NONE
        };

        // Get a list of EGL frame buffer configurations that match specified attributes
        EGLint num_configs;
        success = eglChooseConfig( display_, attribs, &config_, 1, &num_configs );
        if( !success )
        {
            LOG_ERROR_AND_THROW( "Failed to choose config" );
        }

        if( !num_configs )
        {
            //Fall back to 16bit depth buffer
            depth_size_ = 16;
            const EGLint attribs[] =
            {
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //Request opengl ES2.0
                EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                EGL_BLUE_SIZE,  color_size_,
                EGL_GREEN_SIZE, color_size_,
                EGL_RED_SIZE,   color_size_,
                EGL_ALPHA_SIZE, color_size_,
                EGL_DEPTH_SIZE, depth_size_,
                EGL_NONE
            };
            success = eglChooseConfig( display_, attribs, &config_, 1, &num_configs );
            if( !success )
            {
                LOG_ERROR_AND_THROW( "Failed to choose 16-bit depth config" );
            }
        }

        if( !num_configs )
        {
            LOG_ERROR_AND_THROW( "Unable to retrieve EGL config" );
        }

        LOG_INFO_MESSAGE("Chosen EGL config: ", color_size_, " bit color, ", depth_size_, " bit depth");

        surface_ = eglCreateWindowSurface( display_, config_, window_, NULL );
        if( surface_ == EGL_NO_SURFACE )
        {
            LOG_ERROR_AND_THROW( "Failed to create EGLSurface" );
        }

        eglQuerySurface( display_, surface_, EGL_WIDTH, &screen_width_ );
        eglQuerySurface( display_, surface_, EGL_HEIGHT, &screen_height_ );

        /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
        * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
        * As soon as we picked a EGLConfig, we can safely reconfigure the
        * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
        EGLint format;
        eglGetConfigAttrib( display_, config_, EGL_NATIVE_VISUAL_ID, &format );
        ANativeWindow_setBuffersGeometry( window_, 0, 0, format );

        return true;
    }

    bool GLContext::InitEGLContext()
    {
        major_version_ = 3;
        minor_version_ = 1;

        const EGLint context_attribs[] =
        {
            EGL_CONTEXT_CLIENT_VERSION, major_version_,
            EGL_CONTEXT_MINOR_VERSION_KHR, minor_version_,
            EGL_NONE
        };

        LOG_INFO_MESSAGE( "contextAttribs: ", context_attribs[0], ' ', context_attribs[1], '\n' );
        LOG_INFO_MESSAGE( "contextAttribs: ", context_attribs[2], ' ', context_attribs[3], '\n' );

        context_ = eglCreateContext( display_, config_, NULL, context_attribs );
        if( context_ == EGL_NO_CONTEXT )
        {
            LOG_ERROR_AND_THROW( "Failed to create EGLContext" );
        }

        if( eglMakeCurrent( display_, surface_, surface_, context_ ) == EGL_FALSE )
        {
            LOG_ERROR_AND_THROW( "Unable to eglMakeCurrent" );
        }

        context_valid_ = true;
        return true;
    }

    void GLContext::AttachToCurrentEGLContext()
    {
        if( eglGetCurrentContext() == EGL_NO_CONTEXT )
        {
            LOG_ERROR_AND_THROW( "Failed to attach to EGLContext: no active context" );
        }
        context_valid_ = true;
        glGetIntegerv( GL_MAJOR_VERSION, &major_version_ );
        glGetIntegerv( GL_MINOR_VERSION, &minor_version_ );
    }

    GLContext::NativeGLContextType GLContext::GetCurrentNativeGLContext()
    {
        return eglGetCurrentContext();
    }

    void GLContext::InitGLES()
    {
        if( gles_initialized_ )
            return;
        //
        //Initialize OpenGL ES 3 if available
        //
        const char* versionStr = (const char*)glGetString( GL_VERSION );
        LOG_INFO_MESSAGE( "GL Version: ", versionStr, '\n' );

        LoadGLFunctions();

        // When GL_FRAMEBUFFER_SRGB is enabled, and if the destination image is in the sRGB colorspace
        // then OpenGL will assume the shader's output is in the linear RGB colorspace. It will therefore
        // convert the output from linear RGB to sRGB.
        // Any writes to images that are not in the sRGB format should not be affected.
        // Thus this setting should be just set once and left that way
        glEnable(GL_FRAMEBUFFER_SRGB);
        if( glGetError() != GL_NO_ERROR )
            LOG_ERROR_MESSAGE("Failed to enable SRGB framebuffers");

        gles_initialized_ = true;
    }

    bool GLContext::Init( ANativeWindow* window )
    {
        if( egl_context_initialized_ )
            return true;

        //
        //Initialize EGL
        //
        window_ = window;
        if (window != nullptr)
        {
            InitEGLSurface();
            InitEGLContext();
        }
        else
        {
            AttachToCurrentEGLContext();
        }
        InitGLES();

        egl_context_initialized_ = true;

        return true;
    }

    GLContext::GLContext( const EngineGLAttribs &InitAttribs, DeviceCaps &DeviceCaps ) :
        display_( EGL_NO_DISPLAY ),
        surface_( EGL_NO_SURFACE ),
        context_( EGL_NO_CONTEXT ),
        egl_context_initialized_( false ),
        gles_initialized_( false ),
        major_version_(0),
        minor_version_(0)
    {
        auto *NativeWindow = reinterpret_cast<ANativeWindow*>(InitAttribs.pNativeWndHandle);
        Init( NativeWindow );

        FillDeviceCaps(DeviceCaps);
#if 0
        // Creates table of supported extensions strings
        extensions.clear();
        string tmp;
        sint32 begin, end;
        tmp = string( (char*)glGetString( GL_EXTENSIONS ) );

        begin = 0;
        end = tmp.find( ' ', 0 );

        DEBUG_PRINT( _L( "Checking Extensions" ) );

        while( end != string::npos )
        {
            DEBUG_PRINT( (_L( "extension %s" )), tmp.substr( begin, end - begin ).c_str() );
            extensions.insert( extensions.end(), tmp.substr( begin, end - begin ) );
            begin = end + 1;
            end = tmp.find( ' ', begin );
        }

        if( supportExtension( "GL_INTEL_tessellation" ) )
        {
            glPatchParameteri = (PFNGLPATCHPARAMETERIPROC)eglGetProcAddress( "glPatchParameteri" );
            DEBUG_PRINT( _L( "%s = %p" ), "glPatchParameteri", (void*)glPatchParameteri );
            glPatchParameterfv = (PFNGLPATCHPARAMETERFVPROC)eglGetProcAddress( "glPatchParameterfv" );
            DEBUG_PRINT( _L( "%s = %p" ), "glPatchParameterfv", (void*)glPatchParameterfv );
        }
        //if(supportExtension("GL_INTEL_compute_shader"))
        {
            glDispatchCompute = (PFNGLDISPATCHCOMPUTEPROC)eglGetProcAddress( "glDispatchCompute" );
            DEBUG_PRINT( _L( "%s = %p" ), "glDispatchCompute", (void*)glDispatchCompute );
            glBindImageTexture = (PFNGLBINDIMAGETEXTUREPROC)eglGetProcAddress( "glBindImageTexture" );
            DEBUG_PRINT( _L( "%s = %p" ), "glBindImageTexture", (void*)glBindImageTexture );
        }
#endif
    }

    GLContext::~GLContext()
    {
        Terminate();
    }

    void GLContext::SwapBuffers()
    {
        if(surface_ == EGL_NO_SURFACE)
        {
            LOG_WARNING_MESSAGE("No EGL surface when swapping buffers. This happens when SwapBuffers() is called after Suspend(). The operation will be ignored.");
            return;
        }

        bool b = eglSwapBuffers( display_, surface_ );
        if( !b )
        {
            EGLint err = eglGetError();
            if( err == EGL_BAD_SURFACE )
            {
                LOG_INFO_MESSAGE("EGL surface has been lost. Attempting to recreate");
                InitEGLSurface();
                //return EGL_SUCCESS; //Still consider glContext is valid
            }
            else if( err == EGL_CONTEXT_LOST || err == EGL_BAD_CONTEXT )
            {
                //Context has been lost!!
                context_valid_ = false;
                Terminate();
                InitEGLContext();
            }
            //return err;
        }
        //return EGL_SUCCESS;
    }

    void GLContext::Terminate()
    {
        if( display_ != EGL_NO_DISPLAY )
        {
            eglMakeCurrent( display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
            if( context_ != EGL_NO_CONTEXT )
            {
                eglDestroyContext( display_, context_ );
            }

            if( surface_ != EGL_NO_SURFACE )
            {
                eglDestroySurface( display_, surface_ );
            }
            eglTerminate( display_ );
        }

        display_ = EGL_NO_DISPLAY;
        context_ = EGL_NO_CONTEXT;
        surface_ = EGL_NO_SURFACE;
        context_valid_ = false;
    }

    EGLint GLContext::Resume( ANativeWindow* window )
    {
        LOG_INFO_MESSAGE( "Resuming gl context\n" );

        if( egl_context_initialized_ == false )
        {
            Init( window );
            return EGL_SUCCESS;
        }

        //Create surface
        window_ = window;
        surface_ = eglCreateWindowSurface( display_, config_, window_, NULL );
        int32_t new_screen_width  = 0;
        int32_t new_screen_height = 0;
        eglQuerySurface( display_, surface_, EGL_WIDTH, &new_screen_width );
        eglQuerySurface( display_, surface_, EGL_HEIGHT, &new_screen_height );

        if( new_screen_width != screen_width_ || new_screen_height != screen_height_ )
        {
            //Screen resized
            LOG_INFO_MESSAGE( "Screen resized\n" );
        }

        if( eglMakeCurrent( display_, surface_, surface_, context_ ) == EGL_TRUE )
            return EGL_SUCCESS;

        EGLint err = eglGetError();
        LOG_WARNING_MESSAGE( "Unable to eglMakeCurrent ", err, '\n' );

        if( err == EGL_CONTEXT_LOST )
        {
            //Recreate context
            LOG_INFO_MESSAGE( "Re-creating egl context\n" );
            InitEGLContext();
        }
        else
        {
            //Recreate surface
            LOG_INFO_MESSAGE( "Re-creating egl context and surface\n" );
            Terminate();
            InitEGLSurface();
            InitEGLContext();
        }

        return err;
    }

    void GLContext::Suspend()
    {
        LOG_INFO_MESSAGE( "Suspending gl context\n" );
        if( surface_ != EGL_NO_SURFACE )
        {
            LOG_INFO_MESSAGE( "Destroying egl surface\n" );
            eglDestroySurface( display_, surface_ );
            surface_ = EGL_NO_SURFACE;
        }
    }

    bool GLContext::Invalidate()
    {
        LOG_INFO_MESSAGE( "Invalidating gl context\n" );
        Terminate();

        egl_context_initialized_ = false;
        return true;
    }

    void GLContext::FillDeviceCaps( DeviceCaps &DeviceCaps )
    {
        DeviceCaps.DevType = DeviceType::OpenGLES;
        DeviceCaps.MajorVersion = major_version_;
        DeviceCaps.MinorVersion = minor_version_;
        bool IsGLES31OrAbove = (major_version_ >= 4 || (major_version_ == 3 && minor_version_ >= 1) );
        DeviceCaps.bSeparableProgramSupported      = IsGLES31OrAbove;
        DeviceCaps.bIndirectRenderingSupported     = IsGLES31OrAbove;

        auto &SamCaps = DeviceCaps.SamCaps;
        SamCaps.bBorderSamplingModeSupported   = GL_TEXTURE_BORDER_COLOR       && IsGLES31OrAbove;
        SamCaps.bAnisotropicFilteringSupported = GL_TEXTURE_MAX_ANISOTROPY_EXT && IsGLES31OrAbove;
        SamCaps.bLODBiasSupported              = GL_TEXTURE_LOD_BIAS           && IsGLES31OrAbove;

        auto &TexCaps = DeviceCaps.TexCaps;
        TexCaps.bTexture1DSupported            = False;     // Not supported in GLES 3.1
        TexCaps.bTexture1DArraySupported       = False;     // Not supported in GLES 3.1
        TexCaps.bTexture2DMSSupported          = IsGLES31OrAbove;
        TexCaps.bTexture2DMSArraySupported     = False;     // Not supported in GLES 3.1
        TexCaps.bTextureViewSupported          = False;     // Not supported in GLES 3.1
        TexCaps.bCubemapArraysSupported        = False;     // Not supported in GLES 3.1
        DeviceCaps.bMultithreadedResourceCreationSupported = False;
    }
}
<|endoftext|>


<commit_before>#include "TestRunner.h"
#include <atomic>
#include <future>
#include <limits>
#include <mutex>
#include <random>
#include <vector>
#include "DefaultReporter.h"
#include "Fact.h"
#include "TestCollection.h"
#include "TestDetails.h"
#include "xUnitAssert.h"

namespace
{

class ActiveTests
{
public:
    struct TestInstance
    {
        TestInstance(const xUnitpp::TestDetails &testDetails, int id, int groupId, int groupSize, std::function<void()> test)
            : testDetails(testDetails)
            , id(id)
            , groupId(groupId)
            , groupSize(groupSize)
            , test(test)
        {
        }

        TestInstance(const TestInstance &other)
            : testDetails(other.testDetails)
            , id(other.id)
            , groupId(other.groupId)
            , groupSize(other.groupSize)
            , test(other.test)
        {
        }

        TestInstance(TestInstance &&other)
        {
            swap(*this, other);
        }

        TestInstance &operator =(TestInstance other)
        {
            swap(*this, other);
            return *this;
        }

        friend void swap(TestInstance &ti0, TestInstance &ti1)
        {
            using std::swap;

            swap(ti0.testDetails, ti1.testDetails);
            swap(ti0.id, ti1.id);
            swap(ti0.groupId, ti1.groupId);
            swap(ti0.groupSize, ti1.groupSize);
            swap(ti0.test, ti1.test);
        }

        xUnitpp::TestDetails testDetails;

        size_t id;
        size_t groupId;
        size_t groupSize;

        std::function<void()> test;
    };

    ActiveTests(const std::vector<xUnitpp::Fact> &facts, const std::vector<xUnitpp::Theory> &theories, const std::string &suite)
    {
        size_t id = 0;
        size_t groupId = 0;

        for (auto &fact : facts)
        {
            if (suite == "" || fact.TestDetails().Suite == suite)
            {
                mTests.emplace_back(TestInstance(fact.TestDetails(), ++id, ++groupId, 1, fact.Test()));
            }
        }

        for (auto &theorySet : theories)
        {
            if (suite == "" || theorySet.TestDetails().Suite == suite)
            {
                ++groupId;

                for (auto &theory : theorySet.Theories())
                {
                    mTests.emplace_back(TestInstance(theorySet.TestDetails(), ++id, groupId, theorySet.Theories().size(), theory));
                }
            }
        }

        std::shuffle(mTests.begin(), mTests.end(), std::default_random_engine(std::random_device()()));
    }

    std::vector<TestInstance>::iterator begin()
    {
        return mTests.begin();
    }

    std::vector<TestInstance>::iterator end()
    {
        return mTests.end();
    }

private:
    std::vector<TestInstance> mTests;
};

}

namespace xUnitpp
{

class TestRunner::Impl
{
public:
    Impl(std::function<void(const TestDetails &)> onTestStart,
         std::function<void(const TestDetails &, const std::string &)> onTestFailure,
         std::function<void(const TestDetails &, std::chrono::milliseconds)> onTestFinish,
         std::function<void(int, int, int, std::chrono::milliseconds)> onAllTestsComplete)
        : mOnTestStart(onTestStart)
        , mOnTestFailure(onTestFailure)
        , mOnTestFinish(onTestFinish)
        , mOnAllTestsComplete(onAllTestsComplete)
    {
    }

    void OnTestStart(const TestDetails &details)
    {
        std::lock_guard<std::mutex> guard(mStartMtx);
        mOnTestStart(details);
    }

    void OnTestFailure(const TestDetails &details, const std::string &message)
    {
        std::lock_guard<std::mutex> guard(mFailureMtx);
        mOnTestFailure(details, message);
    }

    void OnTestFinish(const TestDetails &details, std::chrono::milliseconds time)
    {
        std::lock_guard<std::mutex> guard(mFinishMtx);
        mOnTestFinish(details, time);
    }

    void OnAllTestsComplete(int total, int skipped, int failed, std::chrono::milliseconds totalTime)
    {
        mOnAllTestsComplete(total, skipped, failed, totalTime);
    }

private:
    std::function<void(const TestDetails &)> mOnTestStart;
    std::function<void(const TestDetails &, const std::string &)> mOnTestFailure;
    std::function<void(const TestDetails &, std::chrono::milliseconds)> mOnTestFinish;
    std::function<void(int, int, int, std::chrono::milliseconds)> mOnAllTestsComplete;

    std::mutex mStartMtx;
    std::mutex mFailureMtx;
    std::mutex mFinishMtx;
};

size_t RunAllTests(const std::string &suite)
{
    return RunAllTests(suite, std::chrono::milliseconds::zero());
}

size_t RunAllTests(std::chrono::milliseconds maxTestRunTime)
{
    return RunAllTests("", maxTestRunTime);
}

size_t RunAllTests(const std::string &suite, std::chrono::milliseconds maxTestRunTime, size_t maxConcurrent)
{
    return
        TestRunner(&DefaultReporter::ReportStart,
                   &DefaultReporter::ReportFailure,
                   &DefaultReporter::ReportFinish,
                   &DefaultReporter::ReportAllTestsComplete)
            .RunTests(TestCollection::Facts(), TestCollection::Theories(), suite, maxTestRunTime, maxConcurrent);
}

TestRunner::TestRunner(std::function<void(const TestDetails &)> onTestStart,
                       std::function<void(const TestDetails &, const std::string &)> onTestFailure,
                       std::function<void(const TestDetails &, std::chrono::milliseconds)> onTestFinish,
                       std::function<void(int, int, int, std::chrono::milliseconds)> onAllTestsComplete)
    : mImpl(new Impl(onTestStart, onTestFailure, onTestFinish, onAllTestsComplete))
{
}

size_t TestRunner::RunTests(const std::vector<Fact> &facts, const std::vector<Theory> &theories, const std::string &suite, std::chrono::milliseconds maxTestRunTime, size_t maxConcurrent)
{
    auto timeStart = std::chrono::system_clock::now();

    ActiveTests activeTests(facts, theories, suite);

    if (maxConcurrent == 0)
    {
        maxConcurrent = std::numeric_limits<decltype(maxConcurrent)>::max();
    }

    class ThreadCounter
    {
    public:
        ThreadCounter(size_t maxThreads)
            : maxThreads(maxThreads)
            , activeThreads(0)
        {
        }

        void operator++()
        {
            std::unique_lock<std::mutex> lock(mtx);
            condition.wait(lock, [&]() { return activeThreads < maxThreads; });

            ++activeThreads;
        }

        void operator--()
        {
            std::lock_guard<std::mutex> guard(mtx);
            --activeThreads;
        }

    private:
        size_t maxThreads;
        size_t activeThreads;
        std::mutex mtx;
        std::condition_variable condition;
    } threadCounter(maxConcurrent);

    std::atomic<size_t> failedTests = 0;

    std::vector<std::future<void>> futures;
    for (auto &test : activeTests)
    {
        futures.push_back(std::async([&]()
            {
                struct CounterGuard
                {
                    CounterGuard(ThreadCounter &tc)
                        : tc(tc)
                    {
                        ++tc;
                    }

                    ~CounterGuard()
                    {
                        --tc;
                    }

                private:
                    ThreadCounter &tc;
                } counterGuard(threadCounter);

                auto actualTest = [&](bool reportEnd) -> TimeStamp
                    {
                        TimeStamp testStart;
                        try
                        {
                            mImpl->OnTestStart(test.testDetails);

                            testStart = Clock::now();
                            test.test();
                        }
                        catch (std::exception &e)
                        {
                            mImpl->OnTestFailure(test.testDetails, e.what());
                            ++failedTests;
                        }
                        catch (...)
                        {
                            mImpl->OnTestFailure(test.testDetails, "Unknown exception caught: test has crashed");
                            ++failedTests;
                        }

                        if (reportEnd)
                        {
                            mImpl->OnTestFinish(test.testDetails, std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - testStart));
                        }

                        return testStart;
                    };

                auto testTimeLimit = test.testDetails.TimeLimit;
                if (testTimeLimit < std::chrono::milliseconds::zero())
                {
                    testTimeLimit = maxTestRunTime;
                }

                if (testTimeLimit > std::chrono::milliseconds::zero())
                {
                    //
                    // note that forcing a test to run in under a certain amount of time is inherently fragile
                    // there's no guarantee that a thread, once started, actually gets `maxTestRunTime` milliseconds of CPU

                    TimeStamp testStart;

                    std::mutex m;
                    std::unique_lock<std::mutex> gate(m);

                    auto threadStarted = std::make_shared<std::condition_variable>();
                    std::thread timedRunner([&, threadStarted]()
                        {
                            m.lock();
                            m.unlock();

                            testStart = actualTest(false);
                            threadStarted->notify_all();
                        });
                    timedRunner.detach();

                    if (threadStarted->wait_for(gate, testTimeLimit) == std::cv_status::timeout)
                    {
                        mImpl->OnTestFailure(test.testDetails, "Test failed to complete within " + std::to_string(testTimeLimit.count()) + " milliseconds.");
                        mImpl->OnTestFinish(test.testDetails, testTimeLimit);
                        ++failedTests;
                    }
                    else
                    {
                        mImpl->OnTestFinish(test.testDetails, std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - testStart));
                    }
                }
                else
                {
                    actualTest(true);
                }
            }));
    }

    for (auto &test : futures)
    {
        test.get();
    }

    mImpl->OnAllTestsComplete(futures.size(), failedTests, 0, std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - timeStart));

    return -failedTests;
}

}
<commit_msg>removing a warning about negating an unsigned<commit_after>#include "TestRunner.h"
#include <atomic>
#include <future>
#include <limits>
#include <mutex>
#include <random>
#include <vector>
#include "DefaultReporter.h"
#include "Fact.h"
#include "TestCollection.h"
#include "TestDetails.h"
#include "xUnitAssert.h"

namespace
{

class ActiveTests
{
public:
    struct TestInstance
    {
        TestInstance(const xUnitpp::TestDetails &testDetails, int id, int groupId, int groupSize, std::function<void()> test)
            : testDetails(testDetails)
            , id(id)
            , groupId(groupId)
            , groupSize(groupSize)
            , test(test)
        {
        }

        TestInstance(const TestInstance &other)
            : testDetails(other.testDetails)
            , id(other.id)
            , groupId(other.groupId)
            , groupSize(other.groupSize)
            , test(other.test)
        {
        }

        TestInstance(TestInstance &&other)
        {
            swap(*this, other);
        }

        TestInstance &operator =(TestInstance other)
        {
            swap(*this, other);
            return *this;
        }

        friend void swap(TestInstance &ti0, TestInstance &ti1)
        {
            using std::swap;

            swap(ti0.testDetails, ti1.testDetails);
            swap(ti0.id, ti1.id);
            swap(ti0.groupId, ti1.groupId);
            swap(ti0.groupSize, ti1.groupSize);
            swap(ti0.test, ti1.test);
        }

        xUnitpp::TestDetails testDetails;

        size_t id;
        size_t groupId;
        size_t groupSize;

        std::function<void()> test;
    };

    ActiveTests(const std::vector<xUnitpp::Fact> &facts, const std::vector<xUnitpp::Theory> &theories, const std::string &suite)
    {
        size_t id = 0;
        size_t groupId = 0;

        for (auto &fact : facts)
        {
            if (suite == "" || fact.TestDetails().Suite == suite)
            {
                mTests.emplace_back(TestInstance(fact.TestDetails(), ++id, ++groupId, 1, fact.Test()));
            }
        }

        for (auto &theorySet : theories)
        {
            if (suite == "" || theorySet.TestDetails().Suite == suite)
            {
                ++groupId;

                for (auto &theory : theorySet.Theories())
                {
                    mTests.emplace_back(TestInstance(theorySet.TestDetails(), ++id, groupId, theorySet.Theories().size(), theory));
                }
            }
        }

        std::shuffle(mTests.begin(), mTests.end(), std::default_random_engine(std::random_device()()));
    }

    std::vector<TestInstance>::iterator begin()
    {
        return mTests.begin();
    }

    std::vector<TestInstance>::iterator end()
    {
        return mTests.end();
    }

private:
    std::vector<TestInstance> mTests;
};

}

namespace xUnitpp
{

class TestRunner::Impl
{
public:
    Impl(std::function<void(const TestDetails &)> onTestStart,
         std::function<void(const TestDetails &, const std::string &)> onTestFailure,
         std::function<void(const TestDetails &, std::chrono::milliseconds)> onTestFinish,
         std::function<void(int, int, int, std::chrono::milliseconds)> onAllTestsComplete)
        : mOnTestStart(onTestStart)
        , mOnTestFailure(onTestFailure)
        , mOnTestFinish(onTestFinish)
        , mOnAllTestsComplete(onAllTestsComplete)
    {
    }

    void OnTestStart(const TestDetails &details)
    {
        std::lock_guard<std::mutex> guard(mStartMtx);
        mOnTestStart(details);
    }

    void OnTestFailure(const TestDetails &details, const std::string &message)
    {
        std::lock_guard<std::mutex> guard(mFailureMtx);
        mOnTestFailure(details, message);
    }

    void OnTestFinish(const TestDetails &details, std::chrono::milliseconds time)
    {
        std::lock_guard<std::mutex> guard(mFinishMtx);
        mOnTestFinish(details, time);
    }

    void OnAllTestsComplete(int total, int skipped, int failed, std::chrono::milliseconds totalTime)
    {
        mOnAllTestsComplete(total, skipped, failed, totalTime);
    }

private:
    std::function<void(const TestDetails &)> mOnTestStart;
    std::function<void(const TestDetails &, const std::string &)> mOnTestFailure;
    std::function<void(const TestDetails &, std::chrono::milliseconds)> mOnTestFinish;
    std::function<void(int, int, int, std::chrono::milliseconds)> mOnAllTestsComplete;

    std::mutex mStartMtx;
    std::mutex mFailureMtx;
    std::mutex mFinishMtx;
};

size_t RunAllTests(const std::string &suite)
{
    return RunAllTests(suite, std::chrono::milliseconds::zero());
}

size_t RunAllTests(std::chrono::milliseconds maxTestRunTime)
{
    return RunAllTests("", maxTestRunTime);
}

size_t RunAllTests(const std::string &suite, std::chrono::milliseconds maxTestRunTime, size_t maxConcurrent)
{
    return
        TestRunner(&DefaultReporter::ReportStart,
                   &DefaultReporter::ReportFailure,
                   &DefaultReporter::ReportFinish,
                   &DefaultReporter::ReportAllTestsComplete)
            .RunTests(TestCollection::Facts(), TestCollection::Theories(), suite, maxTestRunTime, maxConcurrent);
}

TestRunner::TestRunner(std::function<void(const TestDetails &)> onTestStart,
                       std::function<void(const TestDetails &, const std::string &)> onTestFailure,
                       std::function<void(const TestDetails &, std::chrono::milliseconds)> onTestFinish,
                       std::function<void(int, int, int, std::chrono::milliseconds)> onAllTestsComplete)
    : mImpl(new Impl(onTestStart, onTestFailure, onTestFinish, onAllTestsComplete))
{
}

size_t TestRunner::RunTests(const std::vector<Fact> &facts, const std::vector<Theory> &theories, const std::string &suite, std::chrono::milliseconds maxTestRunTime, size_t maxConcurrent)
{
    auto timeStart = std::chrono::system_clock::now();

    ActiveTests activeTests(facts, theories, suite);

    if (maxConcurrent == 0)
    {
        maxConcurrent = std::numeric_limits<decltype(maxConcurrent)>::max();
    }

    class ThreadCounter
    {
    public:
        ThreadCounter(size_t maxThreads)
            : maxThreads(maxThreads)
            , activeThreads(0)
        {
        }

        void operator++()
        {
            std::unique_lock<std::mutex> lock(mtx);
            condition.wait(lock, [&]() { return activeThreads < maxThreads; });

            ++activeThreads;
        }

        void operator--()
        {
            std::lock_guard<std::mutex> guard(mtx);
            --activeThreads;
        }

    private:
        size_t maxThreads;
        size_t activeThreads;
        std::mutex mtx;
        std::condition_variable condition;
    } threadCounter(maxConcurrent);

    std::atomic<int> failedTests = 0;

    std::vector<std::future<void>> futures;
    for (auto &test : activeTests)
    {
        futures.push_back(std::async([&]()
            {
                struct CounterGuard
                {
                    CounterGuard(ThreadCounter &tc)
                        : tc(tc)
                    {
                        ++tc;
                    }

                    ~CounterGuard()
                    {
                        --tc;
                    }

                private:
                    ThreadCounter &tc;
                } counterGuard(threadCounter);

                auto actualTest = [&](bool reportEnd) -> TimeStamp
                    {
                        TimeStamp testStart;
                        try
                        {
                            mImpl->OnTestStart(test.testDetails);

                            testStart = Clock::now();
                            test.test();
                        }
                        catch (std::exception &e)
                        {
                            mImpl->OnTestFailure(test.testDetails, e.what());
                            ++failedTests;
                        }
                        catch (...)
                        {
                            mImpl->OnTestFailure(test.testDetails, "Unknown exception caught: test has crashed");
                            ++failedTests;
                        }

                        if (reportEnd)
                        {
                            mImpl->OnTestFinish(test.testDetails, std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - testStart));
                        }

                        return testStart;
                    };

                auto testTimeLimit = test.testDetails.TimeLimit;
                if (testTimeLimit < std::chrono::milliseconds::zero())
                {
                    testTimeLimit = maxTestRunTime;
                }

                if (testTimeLimit > std::chrono::milliseconds::zero())
                {
                    //
                    // note that forcing a test to run in under a certain amount of time is inherently fragile
                    // there's no guarantee that a thread, once started, actually gets `maxTestRunTime` milliseconds of CPU

                    TimeStamp testStart;

                    std::mutex m;
                    std::unique_lock<std::mutex> gate(m);

                    auto threadStarted = std::make_shared<std::condition_variable>();
                    std::thread timedRunner([&, threadStarted]()
                        {
                            m.lock();
                            m.unlock();

                            testStart = actualTest(false);
                            threadStarted->notify_all();
                        });
                    timedRunner.detach();

                    if (threadStarted->wait_for(gate, testTimeLimit) == std::cv_status::timeout)
                    {
                        mImpl->OnTestFailure(test.testDetails, "Test failed to complete within " + std::to_string(testTimeLimit.count()) + " milliseconds.");
                        mImpl->OnTestFinish(test.testDetails, testTimeLimit);
                        ++failedTests;
                    }
                    else
                    {
                        mImpl->OnTestFinish(test.testDetails, std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - testStart));
                    }
                }
                else
                {
                    actualTest(true);
                }
            }));
    }

    for (auto &test : futures)
    {
        test.get();
    }

    mImpl->OnAllTestsComplete(futures.size(), failedTests, 0, std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - timeStart));

    return -failedTests;
}

}
<|endoftext|>


<commit_before>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2020.  All rights reserved
 */
#ifndef _Stroika_Foundation_Execution_Sleep_inl_
#define _Stroika_Foundation_Execution_Sleep_inl_ 1

/*
 ********************************************************************************
 ***************************** Implementation Details ***************************
 ********************************************************************************
 */

#if qPlatform_Windows
#include <windows.h>
#elif qPlatform_POSIX
#include <time.h>
#include <unistd.h>
#endif

#include "../Debug/Assertions.h"

namespace Stroika::Foundation::Execution {

    //redeclare to avoid having to include Thread code
    void CheckForThreadInterruption ();

    /*
     ********************************************************************************
     ******************************** Execution::Sleep ******************************
     ********************************************************************************
     */
    inline void Sleep (Time::DurationSecondsType seconds2Wait, Time::DurationSecondsType* remainingInSleep)
    {
        Require (seconds2Wait >= 0.0);
        RequireNotNull (remainingInSleep); // else call the over overload
        CheckForThreadInterruption ();
        // @todo lose if the #if stuff and use just if constexpr (but not working on msvc)
#if qPlatform_POSIX
        if constexpr (qPlatform_POSIX) {
            const long kNanoSecondsPerSecond = 1000L * 1000L * 1000L;
            timespec   ts;
            ts.tv_sec  = static_cast<time_t> (seconds2Wait);
            ts.tv_nsec = static_cast<long> (kNanoSecondsPerSecond * (seconds2Wait - ts.tv_sec));
            Assert (0 <= ts.tv_nsec and ts.tv_nsec < kNanoSecondsPerSecond);
            timespec nextTS;
            if (::nanosleep (&ts, &nextTS) == 0) {
                *remainingInSleep = 0;
            }
            else {
                *remainingInSleep = nextTS.tv_sec + static_cast<Time::DurationSecondsType> (ts.tv_nsec) / kNanoSecondsPerSecond;
            }
        }
#elif qPlatform_Windows
        if constexpr (qPlatform_Windows) {
            Time::DurationSecondsType tc = Time::GetTickCount ();
            if (::SleepEx (static_cast<int> (seconds2Wait * 1000), true) == 0) {
                *remainingInSleep = 0;
            }
            else {
                Time::DurationSecondsType remaining = (tc + seconds2Wait) - Time::GetTickCount ();
                if (remaining < 0) {
                    remaining = 0;
                }
                *remainingInSleep = remaining;
            }
        }
#else
        AssertNotImplemented ();
#endif
        Ensure (*remainingInSleep <= seconds2Wait);
        Ensure (*remainingInSleep >= 0);
        CheckForThreadInterruption ();
    }

    /*
     ********************************************************************************
     *************************** Execution::SleepUntil ******************************
     ********************************************************************************
     */
    inline void SleepUntil (Time::DurationSecondsType untilTickCount)
    {
        Time::DurationSecondsType waitMoreSeconds = untilTickCount - Time::GetTickCount ();
        if (waitMoreSeconds <= 0) {
            CheckForThreadInterruption ();
        }
        else {
            Sleep (waitMoreSeconds);
        }
    }

}

#endif /*_Stroika_Foundation_Execution_Sleep_inl_*/
<commit_msg>Execution::Sleep() - WeakAsserts() and workaround for bad values sometimes generated by nanosleep() on linux (under windows WSL - not clarly illegal)<commit_after>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2020.  All rights reserved
 */
#ifndef _Stroika_Foundation_Execution_Sleep_inl_
#define _Stroika_Foundation_Execution_Sleep_inl_ 1

/*
 ********************************************************************************
 ***************************** Implementation Details ***************************
 ********************************************************************************
 */

#if qPlatform_Windows
#include <windows.h>
#elif qPlatform_POSIX
#include <time.h>
#include <unistd.h>
#endif

#include "../Debug/Assertions.h"

namespace Stroika::Foundation::Execution {

    //redeclare to avoid having to include Thread code
    void CheckForThreadInterruption ();

    /*
     ********************************************************************************
     ******************************** Execution::Sleep ******************************
     ********************************************************************************
     */
    inline void Sleep (Time::DurationSecondsType seconds2Wait, Time::DurationSecondsType* remainingInSleep)
    {
        Require (seconds2Wait >= 0.0);
        RequireNotNull (remainingInSleep); // else call the over overload
        CheckForThreadInterruption ();
        // @todo lose if the #if stuff and use just if constexpr (but not working on msvc)
#if qPlatform_POSIX
        if constexpr (qPlatform_POSIX) {
            const long kNanoSecondsPerSecond = 1000L * 1000L * 1000L;
            timespec   ts;
            ts.tv_sec  = static_cast<time_t> (seconds2Wait);
            ts.tv_nsec = static_cast<long> (kNanoSecondsPerSecond * (seconds2Wait - ts.tv_sec));
            Assert (0 <= ts.tv_sec);
            Assert (0 <= ts.tv_nsec and ts.tv_nsec < kNanoSecondsPerSecond);
            timespec nextTS;
            if (::nanosleep (&ts, &nextTS) == 0) {
                *remainingInSleep = 0;
            }
            else {
                // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html doesn't clearly document allowed range for timespec
                // https://pubs.opengroup.org/onlinepubs/9699919799/functions/nanosleep.html doesn't clearly document allowed range for output timespec
                // value (can it go negative)
                // On WSL 1 (WinVer 1909) nextTS sometimes goes negative (or blows up to be very large?), so check)
                WeakAssert (0 <= ts.tv_nsec and ts.tv_nsec < kNanoSecondsPerSecond); // not sure this should happen? - log for now... -- LGP 2020-05-29
                WeakAssert (nextTS.tv_sec >= 0);                                     // ""
                if ((nextTS.tv_sec < 0) or not(0 <= ts.tv_nsec and ts.tv_nsec < kNanoSecondsPerSecond)) {
                    *remainingInSleep = 0;
                }
                else {
                    *remainingInSleep = nextTS.tv_sec + static_cast<Time::DurationSecondsType> (ts.tv_nsec) / kNanoSecondsPerSecond;
                }
                if (*remainingInSleep <= seconds2Wait) {
                    DbgTrace (L"*remainingInSleep <= seconds2Wait failure: remainingInSleep=%f, seconds2Wait=%f, nextTS.tv_sec=%ld, ts.tv_nsec=%ld, ts.tv_sec=%ld, nextTS.tv_nsec=%ld", *remainingInSleep, seconds2Wait, nextTS.tv_sec, ts.tv_nsec, ts.tv_sec, nextTS.tv_nsec);
                }
            }
        }
#elif qPlatform_Windows
        if constexpr (qPlatform_Windows) {
            Time::DurationSecondsType tc = Time::GetTickCount ();
            if (::SleepEx (static_cast<int> (seconds2Wait * 1000), true) == 0) {
                *remainingInSleep = 0;
            }
            else {
                Time::DurationSecondsType remaining = (tc + seconds2Wait) - Time::GetTickCount ();
                if (remaining < 0) {
                    remaining = 0;
                }
                *remainingInSleep = remaining;
            }
        }
#else
        AssertNotImplemented ();
#endif
        Ensure (*remainingInSleep <= seconds2Wait);
        Ensure (*remainingInSleep >= 0);
        CheckForThreadInterruption ();
    }

    /*
     ********************************************************************************
     *************************** Execution::SleepUntil ******************************
     ********************************************************************************
     */
    inline void SleepUntil (Time::DurationSecondsType untilTickCount)
    {
        Time::DurationSecondsType waitMoreSeconds = untilTickCount - Time::GetTickCount ();
        if (waitMoreSeconds <= 0) {
            CheckForThreadInterruption ();
        }
        else {
            Sleep (waitMoreSeconds);
        }
    }

}

#endif /*_Stroika_Foundation_Execution_Sleep_inl_*/
<|endoftext|>