<commit_before>/*
    Copyright (c) 2007 Bruno Virlet <bruno.virlet@gmail.com>

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include <libakonadi/session.h>
#include <libakonadi/monitor.h>
#include <libakonadi/itemfetchjob.h>
#include <libakonadi/item.h>

#include <strigi/qtdbus/strigiclient.h>

#include <QDebug>

#include "mailthreaderagent.h"

#include <kmime/kmime_message.h>
#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<KMime::Message> MessagePtr;

using namespace Akonadi;

class SortCacheItem
{
public:
  DataReference ref;
  int mark; // 0, perfect sort; -1, to sort; -2, don't even try to sort it
  int parentId;
  QList<SortCacheItem*> perfectChildren;
  QMap<int,SortCacheItem*> unperfectChildren;
};

class MailThreaderAgent::Private
{
  public:
  MailThreaderAgent* mParent;
  StrigiClient strigi;
  QList<SortCacheItem*> mRootChildren;
  QMap<int, SortCacheItem*> mItemMap;
  QMap<int, SortCacheItem*> mImperfectlyThreadedItems;
  Private( MailThreaderAgent *parent )
      : mParent( parent )
  {
  }

  // From kmmsgbase.cpp
  QString stripOffPrefixes( const QString& str )
  {
    // ### Used to be configurable in KMail (see kmmsgbase)
    QStringList sReplySubjPrefixes, sForwardSubjPrefixes;
    sReplySubjPrefixes << "Re\\s*:" << "Re\\[\\d+\\]:" << "Re\\d+:";
    sForwardSubjPrefixes << "Fwd:" << "FW:";

    return replacePrefixes( str, sReplySubjPrefixes + sForwardSubjPrefixes,
                            true, QString() ).trimmed();
  }

  // From kmmsgbase.cpp
  QString replacePrefixes( const QString& str,
                                      const QStringList& prefixRegExps,
                                      bool replace,
                                      const QString& newPrefix )
  {
    bool recognized = false;
    // construct a big regexp that
    // 1. is anchored to the beginning of str (sans whitespace)
    // 2. matches at least one of the part regexps in prefixRegExps
    QString bigRegExp = QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*")
                        .arg( prefixRegExps.join(")|(?:") );
    QRegExp rx( bigRegExp, Qt::CaseInsensitive );
    if ( !rx.isValid() ) {
      kWarning(5006) << "KMMessage::replacePrefixes(): bigRegExp = \""
                      << bigRegExp << "\"\n"
                      << "prefix regexp is invalid!" << endl;
      // try good ole Re/Fwd:
      recognized = str.startsWith( newPrefix );
    } else { // valid rx
      QString tmp = str;
      if ( rx.indexIn( tmp ) == 0 ) {
        recognized = true;
        if ( replace )
          return tmp.replace( 0, rx.matchedLength(), newPrefix + ' ' );
      }
    }
    if ( !recognized )
      return newPrefix + ' ' + str;
    else
      return str;
  }

  DataReference findUsingStrigi( QString property, QString value )
  {
    QString query = property + QString::fromLatin1(":") + value;
    QList<StrigiHit> hits = strigi.getHits( query, 1, 0 );
    if ( hits.count() )
    {
      StrigiHit hit = hits[0];
      return Item::fromUrl( hit.uri );
    }
    else
      return DataReference();
  }

  DataReference findParentInCache( const Item& item )
  {
    QByteArray partParent = item.part( PartParent );
    if ( !partParent.isEmpty() )
      return DataReference( item.part( partParent ).toInt(), QString() );
  }

  bool subjectIsPrefixed( const Item& item )
  {
    // ### Implement me
  }

  DataReference findParent( const Item& item, int *mark )
  {
    DataReference parent;
    DataReference ref = item.reference();

    MessagePtr msg = item.payload<MessagePtr>();

    // Try to fetch his perfect parent using Strigi
    parent = findUsingStrigi( QString::fromLatin1("content.ID"), msg->inReplyTo()->asUnicodeString() );
    if ( !parent.isNull() )
    {
      *mark = 0;
      return parent;
    }

    // A perfect parent was not found: try to find one using the References field
    QString secondReplyId = msg->references()->asUnicodeString();
    // references contains two items, use the first one
    // (the second to last reference)
    const int rightAngle = secondReplyId.indexOf( '>' );
    if( rightAngle != -1 )
      secondReplyId.truncate( rightAngle + 1 );

    // Try to fetch his imperfect parent using Strigi
    parent = findUsingStrigi( QString::fromLatin1("content.ID"), secondReplyId );
    if ( !parent.isNull() )
    {
      *mark = -1;
      return parent;
    }
    // An imperfect parent was not found using References. Try using the Subject field.
    parent = findParentBySubject( item );
    if (!parent.isNull() )
    {
      *mark = -1;
      return parent;
    }

    if ( subjectIsPrefixed( item ) )
    { // There is still a hope for you
      *mark = -1;
      return DataReference();
    }

    qDebug() << "There is nothing to see here, move along !" ;
    *mark = -2;
    return DataReference();
  }

  DataReference findParentBySubject( const Item& item )
  {
    DataReference ref = item.reference();
    DataReference parent;
    MessagePtr msg = item.payload<MessagePtr>();

    QString strippedSubject = stripOffPrefixes( msg->subject()->asUnicodeString() );
    if ( strippedSubject.isEmpty() ) // Not worth trying !
      return parent;

    // Let's try by subject, but only if the  subject is prefixed.
    // This is necessary to make for example cvs commit mailing lists
    // work as expected without having to turn threading off alltogether.
    if ( msg->subject()->asUnicodeString() == strippedSubject ) // true if not prefixed
        return parent;

    QString query = QString::fromLatin1("email.subject:") + strippedSubject; // ###
    QList<StrigiHit> hits = strigi.getHits( query, 50, 0 ); // max = 50. A way to change that to unlimited ? Ask Strigi developer
    if ( hits.count() ) {
        /* Iterate over the list of potential parents with the same
         * subject, and take the closest one by date. */
        foreach ( StrigiHit hit, hits ) {
            // make sure it's not ourselves
            DataReference parentRef = Item::fromUrl( hit.uri );
            if ( parentRef == ref ) continue;

            ItemFetchJob *job = new ItemFetchJob( parentRef, mParent->session() );
            job->addFetchPart( Item::PartEnvelope );
            if ( !job->exec() ) continue;

            Item potentialParentItem = job->items()[0];
            MessagePtr mb = potentialParentItem.payload<MessagePtr>();

            int delta = mb->date()->dateTime().secsTo( msg->date()->dateTime() );
            // delta == 0 is not allowed, to avoid circular threading
            // with duplicates.
            if (delta > 0 ) {
                // Don't use parents more than 6 weeks older than us.
                if (delta < 3628899)
                    parent = parentRef;
                break;
            }
        }
    }
    return parent;
  }

};

const QLatin1String MailThreaderAgent::PartParent = QLatin1String( "AkonadiMailThreaderAgentParent" );
const QLatin1String MailThreaderAgent::PartSort = QLatin1String( "AkonadiMailThreaderAgentSort" );

MailThreaderAgent::MailThreaderAgent( const QString &id )
  : AgentBase( id ),
    d( new Private( this ) )
{
  qDebug() << "mailtheaderagent: at your order, sir!" ;

  // Fetch the whole list, thread it.
}

MailThreaderAgent::~MailThreaderAgent()
{
  delete d;
}

void MailThreaderAgent::aboutToQuit()
{

}

void MailThreaderAgent::configure()
{
}

void MailThreaderAgent::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& )
{
  // TODO Mark differently when perfect parenting or unperfect parenting
  /*
  qDebug() << "mailthreaderagent: looking for parent for new item " << item.url();
  DataReference ref = d->findParent( item );
  if ( !ref.isNull() )
  {
    qDebug() << "mailthreaderagent: parent found with id " << ref.id();
    Item modifiedItem = item;
    modifiedItem.addPart( PartParent, QByteArray::number( ref.id() ) );
  }*/
}

void MailThreaderAgent::itemChanged( const Akonadi::Item &item, const QStringList& )
{
  // TODO We only have something to do here if the collection has changed for the item
//   qDebug() << "mailthreaderagent: looking for parent for changed item " << item.url();
//   DataReference ref = d->findParent( item );
//   if ( !ref.isNull() )
//   {
//     qDebug() << "mailthreaderagent: parent found with id " << ref.id();
//     Item modifiedItem = item;
//     modifiedItem.addPart( PartParent, QByteArray::number( ref.id() ) );
//   }
}

void MailThreaderAgent::itemRemoved(const Akonadi::DataReference & ref)
{
  // TODO Find all item who have the removed one as parent and remove their PartParent

}

QList<DataReference> MailThreaderAgent::childrenOf( const Item& item )
{
  QList<DataReference> items; //preallocate
  foreach( SortCacheItem* sItem, d->mItemMap[ item.reference().id() ]->perfectChildren ) {
    items << sItem->ref;
  }
  foreach( SortCacheItem* sItem, d->mItemMap[ item.reference().id() ]->unperfectChildren ) {
    items << sItem->ref;
  }

  return items;
}

void MailThreaderAgent::threadCollection( const Akonadi::Collection &col )
{
  ItemFetchJob *fjob = new ItemFetchJob( col, session() );
  fjob->addFetchPart( PartSort );
  fjob->addFetchPart( PartParent );
  if ( !fjob->exec() )
    return;

  d->mItemMap.clear();
  Item::List items = fjob->items();
  foreach( Item it, items ) {
    SortCacheItem *sItem = new SortCacheItem();
    d->mItemMap[ it.reference().id() ] = sItem; // ### Avoid copying the item using pointers?
  }

  /*
    First reading the "cache" stored in the item parts
  */
  foreach( Item it, items ) {
    bool ok;
    int parentId = it.part( PartParent ).toInt( &ok );
    int sortState = it.part( PartSort ).toInt();
    if ( ok )
    {
      if ( d->mItemMap[ parentId ]->ref.isNull() ) // has a parent part but parent not in collection anymore
      {
        d->mItemMap[ it.reference().id() ]->mark = -1;
      }
      else
      {
        d->mItemMap[ it.reference().id() ]->mark = sortState; // 0  or -1
        d->mItemMap[ it.reference().id() ]->parentId = parentId;
        if ( sortState == 0 )
          d->mItemMap[ parentId ]->perfectChildren << d->mItemMap[ it.reference().id() ];
        else if ( sortState == -1 )
          d->mItemMap[ parentId ]->unperfectChildren[ it.reference().id() ] = d->mItemMap[ it.reference().id()];
      }
    }
    else // no parent part
      if ( sortState == -2 ) // keep this mark if already set
        d->mItemMap[ it.reference().id() ]->mark = -2;
      else
        d->mItemMap[ it.reference().id() ]->mark = -1;
  }

  /*
    Find the parent for items whose part is -1
  */
  foreach( Item item, items ) {
    if ( d->mItemMap[ item.reference().id() ]->mark == 0 )
      continue; // has already his perfect parent !
    int mark;
    DataReference parentRef = d->findParent( item, &mark );
    if ( mark == 0 )
    {
      d->mItemMap[ item.reference().id() ]->mark = 0;
      d->mItemMap[ item.reference().id() ]->parentId = parentRef.id();
      d->mItemMap[ parentRef.id() ]->perfectChildren << d->mItemMap[ item.reference().id() ];
      d->mItemMap[ parentRef.id() ]->unperfectChildren.remove( item.reference().id() );
    }
    else if ( mark == -1 )
    {
      d->mItemMap[ item.reference().id() ]->mark = -1;
      d->mItemMap[ item.reference().id() ]->parentId = parentRef.id();
      d->mItemMap[ parentRef.id() ]->unperfectChildren[ item.reference().id() ] = d->mItemMap[ item.reference().id()];
      d->mImperfectlyThreadedItems[ item.reference().id() ] = d->mItemMap[ item.reference().id() ];
    }
    else
    {
      d->mItemMap[ item.reference().id() ]->mark = -2;
      d->mRootChildren << d->mItemMap[ item.reference().id()];
    }
  }

}

#include "mailthreaderagent.moc"
<commit_msg>return at least *something*<commit_after>/*
    Copyright (c) 2007 Bruno Virlet <bruno.virlet@gmail.com>

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include <libakonadi/session.h>
#include <libakonadi/monitor.h>
#include <libakonadi/itemfetchjob.h>
#include <libakonadi/item.h>

#include <strigi/qtdbus/strigiclient.h>

#include <QDebug>

#include "mailthreaderagent.h"

#include <kmime/kmime_message.h>
#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<KMime::Message> MessagePtr;

using namespace Akonadi;

class SortCacheItem
{
public:
  DataReference ref;
  int mark; // 0, perfect sort; -1, to sort; -2, don't even try to sort it
  int parentId;
  QList<SortCacheItem*> perfectChildren;
  QMap<int,SortCacheItem*> unperfectChildren;
};

class MailThreaderAgent::Private
{
  public:
  MailThreaderAgent* mParent;
  StrigiClient strigi;
  QList<SortCacheItem*> mRootChildren;
  QMap<int, SortCacheItem*> mItemMap;
  QMap<int, SortCacheItem*> mImperfectlyThreadedItems;
  Private( MailThreaderAgent *parent )
      : mParent( parent )
  {
  }

  // From kmmsgbase.cpp
  QString stripOffPrefixes( const QString& str )
  {
    // ### Used to be configurable in KMail (see kmmsgbase)
    QStringList sReplySubjPrefixes, sForwardSubjPrefixes;
    sReplySubjPrefixes << "Re\\s*:" << "Re\\[\\d+\\]:" << "Re\\d+:";
    sForwardSubjPrefixes << "Fwd:" << "FW:";

    return replacePrefixes( str, sReplySubjPrefixes + sForwardSubjPrefixes,
                            true, QString() ).trimmed();
  }

  // From kmmsgbase.cpp
  QString replacePrefixes( const QString& str,
                                      const QStringList& prefixRegExps,
                                      bool replace,
                                      const QString& newPrefix )
  {
    bool recognized = false;
    // construct a big regexp that
    // 1. is anchored to the beginning of str (sans whitespace)
    // 2. matches at least one of the part regexps in prefixRegExps
    QString bigRegExp = QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*")
                        .arg( prefixRegExps.join(")|(?:") );
    QRegExp rx( bigRegExp, Qt::CaseInsensitive );
    if ( !rx.isValid() ) {
      kWarning(5006) << "KMMessage::replacePrefixes(): bigRegExp = \""
                      << bigRegExp << "\"\n"
                      << "prefix regexp is invalid!" << endl;
      // try good ole Re/Fwd:
      recognized = str.startsWith( newPrefix );
    } else { // valid rx
      QString tmp = str;
      if ( rx.indexIn( tmp ) == 0 ) {
        recognized = true;
        if ( replace )
          return tmp.replace( 0, rx.matchedLength(), newPrefix + ' ' );
      }
    }
    if ( !recognized )
      return newPrefix + ' ' + str;
    else
      return str;
  }

  DataReference findUsingStrigi( QString property, QString value )
  {
    QString query = property + QString::fromLatin1(":") + value;
    QList<StrigiHit> hits = strigi.getHits( query, 1, 0 );
    if ( hits.count() )
    {
      StrigiHit hit = hits[0];
      return Item::fromUrl( hit.uri );
    }
    else
      return DataReference();
  }

  DataReference findParentInCache( const Item& item )
  {
    QByteArray partParent = item.part( PartParent );
    if ( !partParent.isEmpty() )
      return DataReference( item.part( partParent ).toInt(), QString() );
  }

  bool subjectIsPrefixed( const Item& item )
  {
    // ### Implement me
    return false;
  }

  DataReference findParent( const Item& item, int *mark )
  {
    DataReference parent;
    DataReference ref = item.reference();

    MessagePtr msg = item.payload<MessagePtr>();

    // Try to fetch his perfect parent using Strigi
    parent = findUsingStrigi( QString::fromLatin1("content.ID"), msg->inReplyTo()->asUnicodeString() );
    if ( !parent.isNull() )
    {
      *mark = 0;
      return parent;
    }

    // A perfect parent was not found: try to find one using the References field
    QString secondReplyId = msg->references()->asUnicodeString();
    // references contains two items, use the first one
    // (the second to last reference)
    const int rightAngle = secondReplyId.indexOf( '>' );
    if( rightAngle != -1 )
      secondReplyId.truncate( rightAngle + 1 );

    // Try to fetch his imperfect parent using Strigi
    parent = findUsingStrigi( QString::fromLatin1("content.ID"), secondReplyId );
    if ( !parent.isNull() )
    {
      *mark = -1;
      return parent;
    }
    // An imperfect parent was not found using References. Try using the Subject field.
    parent = findParentBySubject( item );
    if (!parent.isNull() )
    {
      *mark = -1;
      return parent;
    }

    if ( subjectIsPrefixed( item ) )
    { // There is still a hope for you
      *mark = -1;
      return DataReference();
    }

    qDebug() << "There is nothing to see here, move along !" ;
    *mark = -2;
    return DataReference();
  }

  DataReference findParentBySubject( const Item& item )
  {
    DataReference ref = item.reference();
    DataReference parent;
    MessagePtr msg = item.payload<MessagePtr>();

    QString strippedSubject = stripOffPrefixes( msg->subject()->asUnicodeString() );
    if ( strippedSubject.isEmpty() ) // Not worth trying !
      return parent;

    // Let's try by subject, but only if the  subject is prefixed.
    // This is necessary to make for example cvs commit mailing lists
    // work as expected without having to turn threading off alltogether.
    if ( msg->subject()->asUnicodeString() == strippedSubject ) // true if not prefixed
        return parent;

    QString query = QString::fromLatin1("email.subject:") + strippedSubject; // ###
    QList<StrigiHit> hits = strigi.getHits( query, 50, 0 ); // max = 50. A way to change that to unlimited ? Ask Strigi developer
    if ( hits.count() ) {
        /* Iterate over the list of potential parents with the same
         * subject, and take the closest one by date. */
        foreach ( StrigiHit hit, hits ) {
            // make sure it's not ourselves
            DataReference parentRef = Item::fromUrl( hit.uri );
            if ( parentRef == ref ) continue;

            ItemFetchJob *job = new ItemFetchJob( parentRef, mParent->session() );
            job->addFetchPart( Item::PartEnvelope );
            if ( !job->exec() ) continue;

            Item potentialParentItem = job->items()[0];
            MessagePtr mb = potentialParentItem.payload<MessagePtr>();

            int delta = mb->date()->dateTime().secsTo( msg->date()->dateTime() );
            // delta == 0 is not allowed, to avoid circular threading
            // with duplicates.
            if (delta > 0 ) {
                // Don't use parents more than 6 weeks older than us.
                if (delta < 3628899)
                    parent = parentRef;
                break;
            }
        }
    }
    return parent;
  }

};

const QLatin1String MailThreaderAgent::PartParent = QLatin1String( "AkonadiMailThreaderAgentParent" );
const QLatin1String MailThreaderAgent::PartSort = QLatin1String( "AkonadiMailThreaderAgentSort" );

MailThreaderAgent::MailThreaderAgent( const QString &id )
  : AgentBase( id ),
    d( new Private( this ) )
{
  qDebug() << "mailtheaderagent: at your order, sir!" ;

  // Fetch the whole list, thread it.
}

MailThreaderAgent::~MailThreaderAgent()
{
  delete d;
}

void MailThreaderAgent::aboutToQuit()
{

}

void MailThreaderAgent::configure()
{
}

void MailThreaderAgent::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& )
{
  // TODO Mark differently when perfect parenting or unperfect parenting
  /*
  qDebug() << "mailthreaderagent: looking for parent for new item " << item.url();
  DataReference ref = d->findParent( item );
  if ( !ref.isNull() )
  {
    qDebug() << "mailthreaderagent: parent found with id " << ref.id();
    Item modifiedItem = item;
    modifiedItem.addPart( PartParent, QByteArray::number( ref.id() ) );
  }*/
}

void MailThreaderAgent::itemChanged( const Akonadi::Item &item, const QStringList& )
{
  // TODO We only have something to do here if the collection has changed for the item
//   qDebug() << "mailthreaderagent: looking for parent for changed item " << item.url();
//   DataReference ref = d->findParent( item );
//   if ( !ref.isNull() )
//   {
//     qDebug() << "mailthreaderagent: parent found with id " << ref.id();
//     Item modifiedItem = item;
//     modifiedItem.addPart( PartParent, QByteArray::number( ref.id() ) );
//   }
}

void MailThreaderAgent::itemRemoved(const Akonadi::DataReference & ref)
{
  // TODO Find all item who have the removed one as parent and remove their PartParent

}

QList<DataReference> MailThreaderAgent::childrenOf( const Item& item )
{
  QList<DataReference> items; //preallocate
  foreach( SortCacheItem* sItem, d->mItemMap[ item.reference().id() ]->perfectChildren ) {
    items << sItem->ref;
  }
  foreach( SortCacheItem* sItem, d->mItemMap[ item.reference().id() ]->unperfectChildren ) {
    items << sItem->ref;
  }

  return items;
}

void MailThreaderAgent::threadCollection( const Akonadi::Collection &col )
{
  ItemFetchJob *fjob = new ItemFetchJob( col, session() );
  fjob->addFetchPart( PartSort );
  fjob->addFetchPart( PartParent );
  if ( !fjob->exec() )
    return;

  d->mItemMap.clear();
  Item::List items = fjob->items();
  foreach( Item it, items ) {
    SortCacheItem *sItem = new SortCacheItem();
    d->mItemMap[ it.reference().id() ] = sItem; // ### Avoid copying the item using pointers?
  }

  /*
    First reading the "cache" stored in the item parts
  */
  foreach( Item it, items ) {
    bool ok;
    int parentId = it.part( PartParent ).toInt( &ok );
    int sortState = it.part( PartSort ).toInt();
    if ( ok )
    {
      if ( d->mItemMap[ parentId ]->ref.isNull() ) // has a parent part but parent not in collection anymore
      {
        d->mItemMap[ it.reference().id() ]->mark = -1;
      }
      else
      {
        d->mItemMap[ it.reference().id() ]->mark = sortState; // 0  or -1
        d->mItemMap[ it.reference().id() ]->parentId = parentId;
        if ( sortState == 0 )
          d->mItemMap[ parentId ]->perfectChildren << d->mItemMap[ it.reference().id() ];
        else if ( sortState == -1 )
          d->mItemMap[ parentId ]->unperfectChildren[ it.reference().id() ] = d->mItemMap[ it.reference().id()];
      }
    }
    else // no parent part
      if ( sortState == -2 ) // keep this mark if already set
        d->mItemMap[ it.reference().id() ]->mark = -2;
      else
        d->mItemMap[ it.reference().id() ]->mark = -1;
  }

  /*
    Find the parent for items whose part is -1
  */
  foreach( Item item, items ) {
    if ( d->mItemMap[ item.reference().id() ]->mark == 0 )
      continue; // has already his perfect parent !
    int mark;
    DataReference parentRef = d->findParent( item, &mark );
    if ( mark == 0 )
    {
      d->mItemMap[ item.reference().id() ]->mark = 0;
      d->mItemMap[ item.reference().id() ]->parentId = parentRef.id();
      d->mItemMap[ parentRef.id() ]->perfectChildren << d->mItemMap[ item.reference().id() ];
      d->mItemMap[ parentRef.id() ]->unperfectChildren.remove( item.reference().id() );
    }
    else if ( mark == -1 )
    {
      d->mItemMap[ item.reference().id() ]->mark = -1;
      d->mItemMap[ item.reference().id() ]->parentId = parentRef.id();
      d->mItemMap[ parentRef.id() ]->unperfectChildren[ item.reference().id() ] = d->mItemMap[ item.reference().id()];
      d->mImperfectlyThreadedItems[ item.reference().id() ] = d->mItemMap[ item.reference().id() ];
    }
    else
    {
      d->mItemMap[ item.reference().id() ]->mark = -2;
      d->mRootChildren << d->mItemMap[ item.reference().id()];
    }
  }

}

#include "mailthreaderagent.moc"
<|endoftext|>


<commit_before>/*
 * Copyright 2018 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/core/SkStrikeCache.h"

#include <cctype>

#include "include/core/SkGraphics.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkTraceMemoryDump.h"
#include "include/core/SkTypeface.h"
#include "include/private/SkMutex.h"
#include "include/private/SkTemplates.h"
#include "src/core/SkGlyphRunPainter.h"
#include "src/core/SkScalerCache.h"

bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = false;

SkStrikeCache* SkStrikeCache::GlobalStrikeCache() {
#if !defined(SK_BUILD_FOR_IOS)
    if (gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental) {
        static thread_local auto* cache = new SkStrikeCache;
        return cache;
    }
#endif
    static auto* cache = new SkStrikeCache;
    return cache;
}

SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(sk_sp<Strike> strike)
    : fStrike{std::move(strike)} {}

SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr()
    : fStrike{nullptr} {}

SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(ExclusiveStrikePtr&& o)
    : fStrike{std::move(o.fStrike)} {
    o.fStrike = nullptr;
}

SkStrikeCache::ExclusiveStrikePtr&
SkStrikeCache::ExclusiveStrikePtr::operator = (ExclusiveStrikePtr&& that) {
    fStrike = std::move(that.fStrike);
    return *this;
}

SkStrike* SkStrikeCache::ExclusiveStrikePtr::get() const {
    return fStrike.get();
}

SkStrike* SkStrikeCache::ExclusiveStrikePtr::operator -> () const {
    return this->get();
}

SkStrike& SkStrikeCache::ExclusiveStrikePtr::operator *  () const {
    return *this->get();
}

SkStrikeCache::ExclusiveStrikePtr::operator bool () const {
    return fStrike != nullptr;
}

bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs,
                  const SkStrikeCache::ExclusiveStrikePtr& rhs) {
    return lhs.fStrike == rhs.fStrike;
}

bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs, decltype(nullptr)) {
    return lhs.fStrike == nullptr;
}

bool operator == (decltype(nullptr), const SkStrikeCache::ExclusiveStrikePtr& rhs) {
    return nullptr == rhs.fStrike;
}

SkStrikeCache::~SkStrikeCache() {
    Strike* strike = fHead;
    while (strike) {
        Strike* next = strike->fNext;
        strike->unref();
        strike = next;
    }
}

SkExclusiveStrikePtr SkStrikeCache::findOrCreateStrikeExclusive(
        const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface)
{
    return SkExclusiveStrikePtr(this->findOrCreateStrike(desc, effects, typeface));
}

auto SkStrikeCache::findOrCreateStrike(const SkDescriptor& desc,
                                       const SkScalerContextEffects& effects,
                                       const SkTypeface& typeface) -> sk_sp<Strike> {
    SkAutoSpinlock ac(fLock);
    sk_sp<Strike> strike = this->internalFindStrikeOrNull(desc);
    if (strike == nullptr) {
        auto scaler = typeface.createScalerContext(effects, &desc);
        strike = this->internalCreateStrike(desc, std::move(scaler));
    }
    return strike;
}

SkScopedStrikeForGPU SkStrikeCache::findOrCreateScopedStrike(const SkDescriptor& desc,
                                                             const SkScalerContextEffects& effects,
                                                             const SkTypeface& typeface) {
    return SkScopedStrikeForGPU{this->findOrCreateStrike(desc, effects, typeface).release()};
}

void SkStrikeCache::PurgeAll() {
    GlobalStrikeCache()->purgeAll();
}

void SkStrikeCache::Dump() {
    SkDebugf("GlyphCache [     used    budget ]\n");
    SkDebugf("    bytes  [ %8zu  %8zu ]\n",
             SkGraphics::GetFontCacheUsed(), SkGraphics::GetFontCacheLimit());
    SkDebugf("    count  [ %8zu  %8zu ]\n",
             SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit());

    int counter = 0;

    auto visitor = [&counter](const Strike& strike) {
        const SkScalerContextRec& rec = strike.fScalerCache.getScalerContext()->getRec();

        SkDebugf("index %d\n", counter);
        SkDebugf("%s", rec.dump().c_str());
        counter += 1;
    };

    GlobalStrikeCache()->forEachStrike(visitor);
}

namespace {
    const char gGlyphCacheDumpName[] = "skia/sk_glyph_cache";
}  // namespace

void SkStrikeCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) {
    dump->dumpNumericValue(gGlyphCacheDumpName, "size", "bytes", SkGraphics::GetFontCacheUsed());
    dump->dumpNumericValue(gGlyphCacheDumpName, "budget_size", "bytes",
                           SkGraphics::GetFontCacheLimit());
    dump->dumpNumericValue(gGlyphCacheDumpName, "glyph_count", "objects",
                           SkGraphics::GetFontCacheCountUsed());
    dump->dumpNumericValue(gGlyphCacheDumpName, "budget_glyph_count", "objects",
                           SkGraphics::GetFontCacheCountLimit());

    if (dump->getRequestedDetails() == SkTraceMemoryDump::kLight_LevelOfDetail) {
        dump->setMemoryBacking(gGlyphCacheDumpName, "malloc", nullptr);
        return;
    }

    auto visitor = [&dump](const Strike& strike) {
        const SkTypeface* face = strike.fScalerCache.getScalerContext()->getTypeface();
        const SkScalerContextRec& rec = strike.fScalerCache.getScalerContext()->getRec();

        SkString fontName;
        face->getFamilyName(&fontName);
        // Replace all special characters with '_'.
        for (size_t index = 0; index < fontName.size(); ++index) {
            if (!std::isalnum(fontName[index])) {
                fontName[index] = '_';
            }
        }

        SkString dumpName = SkStringPrintf(
                "%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &strike);

        dump->dumpNumericValue(dumpName.c_str(),
                               "size", "bytes", strike.fMemoryUsed);
        dump->dumpNumericValue(dumpName.c_str(),
                               "glyph_count", "objects",
                               strike.fScalerCache.countCachedGlyphs());
        dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
    };

    GlobalStrikeCache()->forEachStrike(visitor);
}

SkExclusiveStrikePtr SkStrikeCache::findStrikeExclusive(const SkDescriptor& desc) {
    SkAutoSpinlock ac(fLock);
    return SkExclusiveStrikePtr(this->internalFindStrikeOrNull(desc));
}

auto SkStrikeCache::internalFindStrikeOrNull(const SkDescriptor& desc) -> sk_sp<Strike> {
    for (Strike* strike = fHead; strike != nullptr; strike = strike->fNext) {
        if (strike->fScalerCache.getDescriptor() == desc) {
            if (fHead != strike) {
                // Make most recently used
                strike->fPrev->fNext = strike->fNext;
                if (strike->fNext != nullptr) {
                    strike->fNext->fPrev = strike->fPrev;
                } else {
                    fTail = strike->fPrev;
                }
                fHead->fPrev = strike;
                strike->fNext = fHead;
                strike->fPrev = nullptr;
                fHead = strike;
            }

            return sk_ref_sp(strike);
        }
    }

    return nullptr;
}

SkExclusiveStrikePtr SkStrikeCache::createStrikeExclusive(
        const SkDescriptor& desc,
        std::unique_ptr<SkScalerContext> scaler,
        SkFontMetrics* maybeMetrics,
        std::unique_ptr<SkStrikePinner> pinner)
{
    SkAutoSpinlock ac(fLock);
    return SkExclusiveStrikePtr(
            this->internalCreateStrike(desc, std::move(scaler), maybeMetrics, std::move(pinner)));
}

auto SkStrikeCache::internalCreateStrike(
        const SkDescriptor& desc,
        std::unique_ptr<SkScalerContext> scaler,
        SkFontMetrics* maybeMetrics,
        std::unique_ptr<SkStrikePinner> pinner) -> sk_sp<Strike> {
    auto strike =
            sk_make_sp<Strike>(this, desc, std::move(scaler), maybeMetrics, std::move(pinner));
    this->internalAttachToHead(strike);
    return strike;
}

void SkStrikeCache::purgeAll() {
    SkAutoSpinlock ac(fLock);
    this->internalPurge(fTotalMemoryUsed);
}

size_t SkStrikeCache::getTotalMemoryUsed() const {
    SkAutoSpinlock ac(fLock);
    return fTotalMemoryUsed;
}

int SkStrikeCache::getCacheCountUsed() const {
    SkAutoSpinlock ac(fLock);
    return fCacheCount;
}

int SkStrikeCache::getCacheCountLimit() const {
    SkAutoSpinlock ac(fLock);
    return fCacheCountLimit;
}

size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) {
    static const size_t minLimit = 256 * 1024;
    if (newLimit < minLimit) {
        newLimit = minLimit;
    }

    SkAutoSpinlock ac(fLock);

    size_t prevLimit = fCacheSizeLimit;
    fCacheSizeLimit = newLimit;
    this->internalPurge();
    return prevLimit;
}

size_t  SkStrikeCache::getCacheSizeLimit() const {
    SkAutoSpinlock ac(fLock);
    return fCacheSizeLimit;
}

int SkStrikeCache::setCacheCountLimit(int newCount) {
    if (newCount < 0) {
        newCount = 0;
    }

    SkAutoSpinlock ac(fLock);

    int prevCount = fCacheCountLimit;
    fCacheCountLimit = newCount;
    this->internalPurge();
    return prevCount;
}

int SkStrikeCache::getCachePointSizeLimit() const {
    SkAutoSpinlock ac(fLock);
    return fPointSizeLimit;
}

int SkStrikeCache::setCachePointSizeLimit(int newLimit) {
    if (newLimit < 0) {
        newLimit = 0;
    }

    SkAutoSpinlock ac(fLock);

    int prevLimit = fPointSizeLimit;
    fPointSizeLimit = newLimit;
    return prevLimit;
}

void SkStrikeCache::forEachStrike(std::function<void(const Strike&)> visitor) const {
    SkAutoSpinlock ac(fLock);

    this->validate();

    for (Strike* strike = fHead; strike != nullptr; strike = strike->fNext) {
        visitor(*strike);
    }
}

size_t SkStrikeCache::internalPurge(size_t minBytesNeeded) {
    this->validate();

    size_t bytesNeeded = 0;
    if (fTotalMemoryUsed > fCacheSizeLimit) {
        bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit;
    }
    bytesNeeded = std::max(bytesNeeded, minBytesNeeded);
    if (bytesNeeded) {
        // no small purges!
        bytesNeeded = std::max(bytesNeeded, fTotalMemoryUsed >> 2);
    }

    int countNeeded = 0;
    if (fCacheCount > fCacheCountLimit) {
        countNeeded = fCacheCount - fCacheCountLimit;
        // no small purges!
        countNeeded = std::max(countNeeded, fCacheCount >> 2);
    }

    // early exit
    if (!countNeeded && !bytesNeeded) {
        return 0;
    }

    size_t  bytesFreed = 0;
    int     countFreed = 0;

    // Start at the tail and proceed backwards deleting; the list is in LRU
    // order, with unimportant entries at the tail.
    Strike* strike = fTail;
    while (strike != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) {
        Strike* prev = strike->fPrev;

        // Only delete if the strike is not pinned.
        if (strike->fPinner == nullptr || strike->fPinner->canDelete()) {
            bytesFreed += strike->fMemoryUsed;
            countFreed += 1;
            this->internalRemoveStrike(strike);
        }
        strike = prev;
    }

    this->validate();

#ifdef SPEW_PURGE_STATUS
    if (countFreed) {
        SkDebugf("purging %dK from font cache [%d entries]\n",
                 (int)(bytesFreed >> 10), countFreed);
    }
#endif

    return bytesFreed;
}

void SkStrikeCache::internalAttachToHead(sk_sp<Strike> strike) {
    SkASSERT(nullptr == strike->fPrev && nullptr == strike->fNext);

    fCacheCount += 1;
    fTotalMemoryUsed += strike->fMemoryUsed;

    if (fHead) {
        fHead->fPrev = strike.get();
        strike->fNext = fHead;
    }

    if (fTail == nullptr) {
        fTail = strike.get();
    }

    fHead = strike.release(); // Transfer ownership of strike to the cache list.
}

void SkStrikeCache::internalRemoveStrike(Strike* strike) {
    SkASSERT(fCacheCount > 0);
    fCacheCount -= 1;
    fTotalMemoryUsed -= strike->fMemoryUsed;

    if (strike->fPrev) {
        strike->fPrev->fNext = strike->fNext;
    } else {
        fHead = strike->fNext;
    }
    if (strike->fNext) {
        strike->fNext->fPrev = strike->fPrev;
    } else {
        fTail = strike->fPrev;
    }
    strike->fPrev = strike->fNext = nullptr;
    strike->fRemoved = true;
    strike->unref();
}

void SkStrikeCache::validate() const {
#ifdef SK_DEBUG
    size_t computedBytes = 0;
    int computedCount = 0;

    const Strike* strike = fHead;
    while (strike != nullptr) {
        computedBytes += strike->fMemoryUsed;
        computedCount += 1;
        strike = strike->fNext;
    }

    // Can't use SkASSERTF because it looses thread annotations.
    if (fCacheCount != computedCount) {
        SkDebugf("fCacheCount: %d, computedCount: %d", fCacheCount, computedCount);
        SK_ABORT("fCacheCount != computedCount");
    }
    if (fTotalMemoryUsed != computedBytes) {
        SkDebugf("fTotalMemoryUsed: %d, computedBytes: %d", fTotalMemoryUsed, computedBytes);
        SK_ABORT("fTotalMemoryUsed == computedBytes");
    }
#endif
}

void SkStrikeCache::Strike::updateDelta(size_t increase) {
    if (increase != 0) {
        SkAutoSpinlock lock{fStrikeCache->fLock};
        fMemoryUsed += increase;
        if (!fRemoved) {
            fStrikeCache->fTotalMemoryUsed += increase;
        }
    }
}
<commit_msg>Snap for 6617762 from 58ebf05ec50f8fcc87d4e5d0fbfb898a73510a72 to rvc-release<commit_after>/*
 * Copyright 2018 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/core/SkStrikeCache.h"

#include <cctype>

#include "include/core/SkGraphics.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkTraceMemoryDump.h"
#include "include/core/SkTypeface.h"
#include "include/private/SkMutex.h"
#include "include/private/SkTemplates.h"
#include "src/core/SkGlyphRunPainter.h"
#include "src/core/SkScalerCache.h"

bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = false;

SkStrikeCache* SkStrikeCache::GlobalStrikeCache() {
#if !defined(SK_BUILD_FOR_IOS)
    if (gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental) {
        static thread_local auto* cache = new SkStrikeCache;
        return cache;
    }
#endif
    static auto* cache = new SkStrikeCache;
    return cache;
}

SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(sk_sp<Strike> strike)
    : fStrike{std::move(strike)} {}

SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr()
    : fStrike{nullptr} {}

SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(ExclusiveStrikePtr&& o)
    : fStrike{std::move(o.fStrike)} {
    o.fStrike = nullptr;
}

SkStrikeCache::ExclusiveStrikePtr&
SkStrikeCache::ExclusiveStrikePtr::operator = (ExclusiveStrikePtr&& that) {
    fStrike = std::move(that.fStrike);
    return *this;
}

SkStrike* SkStrikeCache::ExclusiveStrikePtr::get() const {
    return fStrike.get();
}

SkStrike* SkStrikeCache::ExclusiveStrikePtr::operator -> () const {
    return this->get();
}

SkStrike& SkStrikeCache::ExclusiveStrikePtr::operator *  () const {
    return *this->get();
}

SkStrikeCache::ExclusiveStrikePtr::operator bool () const {
    return fStrike != nullptr;
}

bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs,
                  const SkStrikeCache::ExclusiveStrikePtr& rhs) {
    return lhs.fStrike == rhs.fStrike;
}

bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs, decltype(nullptr)) {
    return lhs.fStrike == nullptr;
}

bool operator == (decltype(nullptr), const SkStrikeCache::ExclusiveStrikePtr& rhs) {
    return nullptr == rhs.fStrike;
}

SkStrikeCache::~SkStrikeCache() {
    Strike* strike = fHead;
    while (strike) {
        Strike* next = strike->fNext;
        strike->unref();
        strike = next;
    }
}

SkExclusiveStrikePtr SkStrikeCache::findOrCreateStrikeExclusive(
        const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface)
{
    return SkExclusiveStrikePtr(this->findOrCreateStrike(desc, effects, typeface));
}

auto SkStrikeCache::findOrCreateStrike(const SkDescriptor& desc,
                                       const SkScalerContextEffects& effects,
                                       const SkTypeface& typeface) -> sk_sp<Strike> {
    SkAutoSpinlock ac(fLock);
    sk_sp<Strike> strike = this->internalFindStrikeOrNull(desc);
    if (strike == nullptr) {
        auto scaler = typeface.createScalerContext(effects, &desc);
        strike = this->internalCreateStrike(desc, std::move(scaler));
    }
    this->internalPurge();
    return strike;
}

SkScopedStrikeForGPU SkStrikeCache::findOrCreateScopedStrike(const SkDescriptor& desc,
                                                             const SkScalerContextEffects& effects,
                                                             const SkTypeface& typeface) {
    return SkScopedStrikeForGPU{this->findOrCreateStrike(desc, effects, typeface).release()};
}

void SkStrikeCache::PurgeAll() {
    GlobalStrikeCache()->purgeAll();
}

void SkStrikeCache::Dump() {
    SkDebugf("GlyphCache [     used    budget ]\n");
    SkDebugf("    bytes  [ %8zu  %8zu ]\n",
             SkGraphics::GetFontCacheUsed(), SkGraphics::GetFontCacheLimit());
    SkDebugf("    count  [ %8zu  %8zu ]\n",
             SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit());

    int counter = 0;

    auto visitor = [&counter](const Strike& strike) {
        const SkScalerContextRec& rec = strike.fScalerCache.getScalerContext()->getRec();

        SkDebugf("index %d\n", counter);
        SkDebugf("%s", rec.dump().c_str());
        counter += 1;
    };

    GlobalStrikeCache()->forEachStrike(visitor);
}

namespace {
    const char gGlyphCacheDumpName[] = "skia/sk_glyph_cache";
}  // namespace

void SkStrikeCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) {
    dump->dumpNumericValue(gGlyphCacheDumpName, "size", "bytes", SkGraphics::GetFontCacheUsed());
    dump->dumpNumericValue(gGlyphCacheDumpName, "budget_size", "bytes",
                           SkGraphics::GetFontCacheLimit());
    dump->dumpNumericValue(gGlyphCacheDumpName, "glyph_count", "objects",
                           SkGraphics::GetFontCacheCountUsed());
    dump->dumpNumericValue(gGlyphCacheDumpName, "budget_glyph_count", "objects",
                           SkGraphics::GetFontCacheCountLimit());

    if (dump->getRequestedDetails() == SkTraceMemoryDump::kLight_LevelOfDetail) {
        dump->setMemoryBacking(gGlyphCacheDumpName, "malloc", nullptr);
        return;
    }

    auto visitor = [&dump](const Strike& strike) {
        const SkTypeface* face = strike.fScalerCache.getScalerContext()->getTypeface();
        const SkScalerContextRec& rec = strike.fScalerCache.getScalerContext()->getRec();

        SkString fontName;
        face->getFamilyName(&fontName);
        // Replace all special characters with '_'.
        for (size_t index = 0; index < fontName.size(); ++index) {
            if (!std::isalnum(fontName[index])) {
                fontName[index] = '_';
            }
        }

        SkString dumpName = SkStringPrintf(
                "%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &strike);

        dump->dumpNumericValue(dumpName.c_str(),
                               "size", "bytes", strike.fMemoryUsed);
        dump->dumpNumericValue(dumpName.c_str(),
                               "glyph_count", "objects",
                               strike.fScalerCache.countCachedGlyphs());
        dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
    };

    GlobalStrikeCache()->forEachStrike(visitor);
}

SkExclusiveStrikePtr SkStrikeCache::findStrikeExclusive(const SkDescriptor& desc) {
    SkAutoSpinlock ac(fLock);
    sk_sp<SkStrike> result = this->internalFindStrikeOrNull(desc);
    this->internalPurge();
    return SkExclusiveStrikePtr(result);
}

auto SkStrikeCache::internalFindStrikeOrNull(const SkDescriptor& desc) -> sk_sp<Strike> {
    for (Strike* strike = fHead; strike != nullptr; strike = strike->fNext) {
        if (strike->fScalerCache.getDescriptor() == desc) {
            if (fHead != strike) {
                // Make most recently used
                strike->fPrev->fNext = strike->fNext;
                if (strike->fNext != nullptr) {
                    strike->fNext->fPrev = strike->fPrev;
                } else {
                    fTail = strike->fPrev;
                }
                fHead->fPrev = strike;
                strike->fNext = fHead;
                strike->fPrev = nullptr;
                fHead = strike;
            }

            return sk_ref_sp(strike);
        }
    }

    return nullptr;
}

SkExclusiveStrikePtr SkStrikeCache::createStrikeExclusive(
        const SkDescriptor& desc,
        std::unique_ptr<SkScalerContext> scaler,
        SkFontMetrics* maybeMetrics,
        std::unique_ptr<SkStrikePinner> pinner)
{
    SkAutoSpinlock ac(fLock);
    return SkExclusiveStrikePtr(
            this->internalCreateStrike(desc, std::move(scaler), maybeMetrics, std::move(pinner)));
}

auto SkStrikeCache::internalCreateStrike(
        const SkDescriptor& desc,
        std::unique_ptr<SkScalerContext> scaler,
        SkFontMetrics* maybeMetrics,
        std::unique_ptr<SkStrikePinner> pinner) -> sk_sp<Strike> {
    auto strike =
            sk_make_sp<Strike>(this, desc, std::move(scaler), maybeMetrics, std::move(pinner));
    this->internalAttachToHead(strike);
    return strike;
}

void SkStrikeCache::purgeAll() {
    SkAutoSpinlock ac(fLock);
    this->internalPurge(fTotalMemoryUsed);
}

size_t SkStrikeCache::getTotalMemoryUsed() const {
    SkAutoSpinlock ac(fLock);
    return fTotalMemoryUsed;
}

int SkStrikeCache::getCacheCountUsed() const {
    SkAutoSpinlock ac(fLock);
    return fCacheCount;
}

int SkStrikeCache::getCacheCountLimit() const {
    SkAutoSpinlock ac(fLock);
    return fCacheCountLimit;
}

size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) {
    SkAutoSpinlock ac(fLock);

    size_t prevLimit = fCacheSizeLimit;
    fCacheSizeLimit = newLimit;
    this->internalPurge();
    return prevLimit;
}

size_t  SkStrikeCache::getCacheSizeLimit() const {
    SkAutoSpinlock ac(fLock);
    return fCacheSizeLimit;
}

int SkStrikeCache::setCacheCountLimit(int newCount) {
    if (newCount < 0) {
        newCount = 0;
    }

    SkAutoSpinlock ac(fLock);

    int prevCount = fCacheCountLimit;
    fCacheCountLimit = newCount;
    this->internalPurge();
    return prevCount;
}

int SkStrikeCache::getCachePointSizeLimit() const {
    SkAutoSpinlock ac(fLock);
    return fPointSizeLimit;
}

int SkStrikeCache::setCachePointSizeLimit(int newLimit) {
    if (newLimit < 0) {
        newLimit = 0;
    }

    SkAutoSpinlock ac(fLock);

    int prevLimit = fPointSizeLimit;
    fPointSizeLimit = newLimit;
    return prevLimit;
}

void SkStrikeCache::forEachStrike(std::function<void(const Strike&)> visitor) const {
    SkAutoSpinlock ac(fLock);

    this->validate();

    for (Strike* strike = fHead; strike != nullptr; strike = strike->fNext) {
        visitor(*strike);
    }
}

size_t SkStrikeCache::internalPurge(size_t minBytesNeeded) {
    this->validate();

    size_t bytesNeeded = 0;
    if (fTotalMemoryUsed > fCacheSizeLimit) {
        bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit;
    }
    bytesNeeded = std::max(bytesNeeded, minBytesNeeded);
    if (bytesNeeded) {
        // no small purges!
        bytesNeeded = std::max(bytesNeeded, fTotalMemoryUsed >> 2);
    }

    int countNeeded = 0;
    if (fCacheCount > fCacheCountLimit) {
        countNeeded = fCacheCount - fCacheCountLimit;
        // no small purges!
        countNeeded = std::max(countNeeded, fCacheCount >> 2);
    }

    // early exit
    if (!countNeeded && !bytesNeeded) {
        return 0;
    }

    size_t  bytesFreed = 0;
    int     countFreed = 0;

    // Start at the tail and proceed backwards deleting; the list is in LRU
    // order, with unimportant entries at the tail.
    Strike* strike = fTail;
    while (strike != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) {
        Strike* prev = strike->fPrev;

        // Only delete if the strike is not pinned.
        if (strike->fPinner == nullptr || strike->fPinner->canDelete()) {
            bytesFreed += strike->fMemoryUsed;
            countFreed += 1;
            this->internalRemoveStrike(strike);
        }
        strike = prev;
    }

    this->validate();

#ifdef SPEW_PURGE_STATUS
    if (countFreed) {
        SkDebugf("purging %dK from font cache [%d entries]\n",
                 (int)(bytesFreed >> 10), countFreed);
    }
#endif

    return bytesFreed;
}

void SkStrikeCache::internalAttachToHead(sk_sp<Strike> strike) {
    SkASSERT(nullptr == strike->fPrev && nullptr == strike->fNext);

    fCacheCount += 1;
    fTotalMemoryUsed += strike->fMemoryUsed;

    if (fHead) {
        fHead->fPrev = strike.get();
        strike->fNext = fHead;
    }

    if (fTail == nullptr) {
        fTail = strike.get();
    }

    fHead = strike.release(); // Transfer ownership of strike to the cache list.
}

void SkStrikeCache::internalRemoveStrike(Strike* strike) {
    SkASSERT(fCacheCount > 0);
    fCacheCount -= 1;
    fTotalMemoryUsed -= strike->fMemoryUsed;

    if (strike->fPrev) {
        strike->fPrev->fNext = strike->fNext;
    } else {
        fHead = strike->fNext;
    }
    if (strike->fNext) {
        strike->fNext->fPrev = strike->fPrev;
    } else {
        fTail = strike->fPrev;
    }
    strike->fPrev = strike->fNext = nullptr;
    strike->fRemoved = true;
    strike->unref();
}

void SkStrikeCache::validate() const {
#ifdef SK_DEBUG
    size_t computedBytes = 0;
    int computedCount = 0;

    const Strike* strike = fHead;
    while (strike != nullptr) {
        computedBytes += strike->fMemoryUsed;
        computedCount += 1;
        strike = strike->fNext;
    }

    // Can't use SkASSERTF because it looses thread annotations.
    if (fCacheCount != computedCount) {
        SkDebugf("fCacheCount: %d, computedCount: %d", fCacheCount, computedCount);
        SK_ABORT("fCacheCount != computedCount");
    }
    if (fTotalMemoryUsed != computedBytes) {
        SkDebugf("fTotalMemoryUsed: %d, computedBytes: %d", fTotalMemoryUsed, computedBytes);
        SK_ABORT("fTotalMemoryUsed == computedBytes");
    }
#endif
}

void SkStrikeCache::Strike::updateDelta(size_t increase) {
    if (increase != 0) {
        SkAutoSpinlock lock{fStrikeCache->fLock};
        fMemoryUsed += increase;
        if (!fRemoved) {
            fStrikeCache->fTotalMemoryUsed += increase;
        }
    }
}
<|endoftext|>


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

// object that creates and contains a spawn position

#include "common.h"

#include "SpawnPosition.h"
#include "FlagInfo.h"
#include "TeamBases.h"
#include "WorldInfo.h"
#include "PlayerInfo.h"
#include "PlayerState.h"

// FIXME: from bzfs.cxx
extern int getCurMaxPlayers();
extern bool areFoes(TeamColor team1, TeamColor team2);
extern BasesList bases;
extern WorldInfo *world;
extern PlayerInfo player[];
extern PlayerState lastState[];

SpawnPosition::SpawnPosition(int playerId, bool onGroundOnly, bool notNearEdges) :
		curMaxPlayers(getCurMaxPlayers())
{
  team = player[playerId].getTeam();
  azimuth = (float)bzfrand() * 2.0f * M_PI;

  if (player[playerId].shouldRestartAtBase() &&
      (team >= RedTeam) && (team <= PurpleTeam) &&
      (bases.find(team) != bases.end())) {
    TeamBases &teamBases = bases[team];
    const TeamBase &base = teamBases.getRandomBase((int)(bzfrand() * 100));
    base.getRandomPosition(pos[0], pos[1], pos[2]);
    player[playerId].setRestartOnBase(false);
  } else {
    const float tankHeight = BZDB.eval(StateDatabase::BZDB_TANKHEIGHT);
    const float tankRadius = BZDB.eval(StateDatabase::BZDB_TANKRADIUS);
    safeSWRadius = (float)((BZDB.eval(StateDatabase::BZDB_SHOCKOUTRADIUS) + BZDB.eval(StateDatabase::BZDB_TANKRADIUS)) * 1.5);
    safeDistance = tankRadius * 20; // FIXME: is this a good value?
    const float size = BZDB.eval(StateDatabase::BZDB_WORLDSIZE);
    const float maxWorldHeight = world->getMaxWorldHeight();
    ObstacleLocation *building = NULL;

    // keep track of how much time we spend searching for a location
    TimeKeeper start = TimeKeeper::getCurrent();

    int inAirAttempts = 50;
    int tries = 0;
    float minProximity = size / 3.0f;
    float bestDist = -1.0f;
    bool foundspot = false;
    while (!foundspot) {
      if (!world->getZonePoint(std::string(Team::getName(team)), testPos)) {
        if (notNearEdges) {
          // don't spawn close to map edges in CTF mode
          testPos[0] = ((float)bzfrand() - 0.5f) * size * 0.6f;
          testPos[1] = ((float)bzfrand() - 0.5f) * size * 0.6f;
	} else {
          testPos[0] = ((float)bzfrand() - 0.5f) * (size - 2.0f * tankRadius);
          testPos[1] = ((float)bzfrand() - 0.5f) * (size - 2.0f * tankRadius);
	}
        testPos[2] = onGroundOnly ? 0.0f : ((float)bzfrand() * maxWorldHeight);
      }
      tries++;

      int type = world->inBuilding(&building, testPos[0], testPos[1], testPos[2],
                                   tankRadius, tankHeight);

      if (onGroundOnly) {
        if (type == NOT_IN_BUILDING)
          foundspot = true;
      } else {
        if ((type == NOT_IN_BUILDING) && (testPos[2] > 0.0f)) {
          testPos[2] = 0.0f;
          //Find any intersection regardless of z
          type = world->inBuilding(&building, testPos[0], testPos[1], testPos[2],
                                   tankRadius, maxWorldHeight);
        }

        // in a building? try climbing on roof until on top
        int lastType = type;
	int retriesRemaining = 100; // don't climb forever
        while (type != NOT_IN_BUILDING) {
          testPos[2] = building->pos[2] + building->size[2] + 0.0001f;
          tries++;
          lastType = type;
          type = world->inBuilding(&building, testPos[0], testPos[1], testPos[2],
                                   tankRadius, tankHeight);
	  if (--retriesRemaining <= 0) {
	    DEBUG1("Warning: getSpawnLocation had to climb too many buildings\n");
	    break;
	  }
        }
        // ok, when not on top of pyramid or teleporter
        if (lastType != IN_PYRAMID  &&  lastType != IN_TELEPORTER) {
          foundspot = true;
        }
        // only try up in the sky so many times
        if (--inAirAttempts <= 0) {
          onGroundOnly = true;
        }
      }

      // check every now and then if we have already used up 10ms of time
      if (tries >= 50) {
        tries = 0;
        if (TimeKeeper::getCurrent() - start > 0.01f) {
          if (bestDist < 0.0f) { // haven't found a single spot
            //Just drop the sucka in, and pray
            pos[0] = testPos[0];
            pos[1] = testPos[1];
            pos[2] = maxWorldHeight;
	    DEBUG1("Warning: getSpawnLocation ran out of time, just dropping the sucker in\n");
          }
          break;
        }
      }

      // check if spot is safe enough
      bool dangerous = isImminentlyDangerous();
      if (foundspot && !dangerous) {
	float enemyAngle;
	float dist = enemyProximityCheck(enemyAngle);
	if (dist > bestDist) { // best so far
	  bestDist = dist;
	  pos[0] = testPos[0];
	  pos[1] = testPos[1];
	  pos[2] = testPos[2];
	  azimuth = fmod((enemyAngle + M_PI), 2.0f * M_PI);
	}
	if (bestDist < minProximity) { // not good enough, keep looking
	  foundspot = false;
	  minProximity *= 0.99f; // relax requirements a little
	}
      } else if (dangerous) {
	foundspot = false;
      }
    }
    if (!(building == NULL))
      delete building;
  }
}

SpawnPosition::~SpawnPosition()
{
}

const bool SpawnPosition::isFacing(const float *enemyPos, const float enemyAzimuth,
				   const float deviation) const
{
  // determine angle from source to dest
  // (X) using only x & y, resultant will be z rotation
  float dx = testPos[0] - enemyPos[0];
  float dy = testPos[1] - enemyPos[1];
  float angActual;
  if (dx == 0) {
    // avoid divide by zero error
    angActual = (float)tan(dy / (1 / 1e12f));
  } else {
    angActual = (float)tan(dy / dx);
  }

  // see if our heading angle is within the bounds set by deviation
  // (X) only compare to z-rotation since that's all we're using
  if (((angActual + deviation / 2) > enemyAzimuth) &&
      ((angActual - deviation / 2) < enemyAzimuth)) {
    return true;
  } else {
    return false;
  }
}

const bool SpawnPosition::isImminentlyDangerous() const
{
  for (int i = 0; i < curMaxPlayers; i++) {
    if (player[i].isAlive() && areFoes(player[i].getTeam(), team)) {
      const FlagInfo *finfo =&flag[player[i].getFlag()];
      const FlagType *ftype = finfo->flag.type;
      float *enemyPos = lastState[i].pos;
      float enemyAngle = lastState[i].azimuth;
      // check for dangerous flags, etc
      // FIXME: any more?
      if (ftype == Flags::Laser) {  // don't spawn in the line of sight of an L
	if (isFacing(enemyPos, enemyAngle, M_PI / 9)) { // he's looking within 20 degrees of spawn point
	  return true;	// eek, don't spawn here
	}
      } else if (ftype == Flags::ShockWave) {  // don't spawn next to a SW
	if (distanceFrom(enemyPos) < safeSWRadius) { // too close to SW
	  return true;	// eek, don't spawn here
	}
      }
      // don't spawn in the line of sight of a normal-shot tank within a certain distance
      if (distanceFrom(enemyPos) < safeDistance) { // within danger zone?
	if (isFacing(enemyPos, enemyAngle, M_PI / 9)) { //and he's looking at me
	  return true;
	}
      }
    }
  }

  // TODO: should check world weapons also

  return false;
}

const float SpawnPosition::enemyProximityCheck(float &enemyAngle) const
{
  float worstDist = 1e12f; // huge number
  bool noEnemy    = true;

  for (int i = 0; i < curMaxPlayers; i++) {
    if (player[i].isAlive() && areFoes(player[i].getTeam(), team)) {
      float *enemyPos = lastState[i].pos;
      if (fabs(enemyPos[2] - testPos[2]) < 1.0f) {
        float x = enemyPos[0] - testPos[0];
        float y = enemyPos[1] - testPos[1];
        float distSq = x * x + y * y;
        if (distSq < worstDist) {
          worstDist  = distSq;
	  enemyAngle = lastState[i].azimuth;
	  noEnemy    = false;
	}
      }
    }
  }
  if (noEnemy)
    enemyAngle = (float)bzfrand() * 2.0f * M_PI;
  return sqrtf(worstDist);
}

const float SpawnPosition::distanceFrom(const float* farPos) const
{
  float dx = farPos[0] - testPos[0];
  float dy = farPos[1] - testPos[1];
  float dz = farPos[2] - testPos[2];
  return (float)sqrt(dx*dx + dy*dy + dz*dz);
}

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

// object that creates and contains a spawn position

#include "common.h"

#include "SpawnPosition.h"
#include "FlagInfo.h"
#include "TeamBases.h"
#include "WorldInfo.h"
#include "PlayerInfo.h"
#include "PlayerState.h"

// FIXME: from bzfs.cxx
extern int getCurMaxPlayers();
extern bool areFoes(TeamColor team1, TeamColor team2);
extern BasesList bases;
extern WorldInfo *world;
extern PlayerInfo player[];
extern PlayerState lastState[];

SpawnPosition::SpawnPosition(int playerId, bool onGroundOnly, bool notNearEdges) :
		curMaxPlayers(getCurMaxPlayers())
{
  team = player[playerId].getTeam();
  azimuth = (float)bzfrand() * 2.0f * M_PI;

  if (player[playerId].shouldRestartAtBase() &&
      (team >= RedTeam) && (team <= PurpleTeam) &&
      (bases.find(team) != bases.end())) {
    TeamBases &teamBases = bases[team];
    const TeamBase &base = teamBases.getRandomBase((int)(bzfrand() * 100));
    base.getRandomPosition(pos[0], pos[1], pos[2]);
    player[playerId].setRestartOnBase(false);
  } else {
    const float tankHeight = BZDB.eval(StateDatabase::BZDB_TANKHEIGHT);
    const float tankRadius = BZDB.eval(StateDatabase::BZDB_TANKRADIUS);
    safeSWRadius = (float)((BZDB.eval(StateDatabase::BZDB_SHOCKOUTRADIUS) + BZDB.eval(StateDatabase::BZDB_TANKRADIUS)) * 1.5);
    safeDistance = tankRadius * 20; // FIXME: is this a good value?
    const float size = BZDB.eval(StateDatabase::BZDB_WORLDSIZE);
    const float maxWorldHeight = world->getMaxWorldHeight();
    ObstacleLocation *building = NULL;

    // keep track of how much time we spend searching for a location
    TimeKeeper start = TimeKeeper::getCurrent();

    int inAirAttempts = 50;
    int tries = 0;
    float minProximity = size / 3.0f;
    float bestDist = -1.0f;
    bool foundspot = false;
    while (!foundspot) {
      if (!world->getZonePoint(std::string(Team::getName(team)), testPos)) {
        if (notNearEdges) {
          // don't spawn close to map edges in CTF mode
          testPos[0] = ((float)bzfrand() - 0.5f) * size * 0.6f;
          testPos[1] = ((float)bzfrand() - 0.5f) * size * 0.6f;
	} else {
          testPos[0] = ((float)bzfrand() - 0.5f) * (size - 2.0f * tankRadius);
          testPos[1] = ((float)bzfrand() - 0.5f) * (size - 2.0f * tankRadius);
	}
        testPos[2] = onGroundOnly ? 0.0f : ((float)bzfrand() * maxWorldHeight);
      }
      tries++;

      int type = world->inBuilding(&building, testPos[0], testPos[1], testPos[2],
                                   tankRadius, tankHeight);

      if (onGroundOnly) {
        if (type == NOT_IN_BUILDING)
          foundspot = true;
      } else {
        if ((type == NOT_IN_BUILDING) && (testPos[2] > 0.0f)) {
          testPos[2] = 0.0f;
          //Find any intersection regardless of z
          type = world->inBuilding(&building, testPos[0], testPos[1], testPos[2],
                                   tankRadius, maxWorldHeight);
        }

        // in a building? try climbing on roof until on top
        int lastType = type;
	int retriesRemaining = 100; // don't climb forever
        while (type != NOT_IN_BUILDING) {
          testPos[2] = building->pos[2] + building->size[2] + 0.0001f;
          tries++;
          lastType = type;
          type = world->inBuilding(&building, testPos[0], testPos[1], testPos[2],
                                   tankRadius, tankHeight);
	  if (--retriesRemaining <= 0) {
	    DEBUG1("Warning: getSpawnLocation had to climb too many buildings\n");
	    break;
	  }
        }
        // ok, when not on top of pyramid or teleporter
        if (lastType != IN_PYRAMID  &&  lastType != IN_TELEPORTER) {
          foundspot = true;
        }
        // only try up in the sky so many times
        if (--inAirAttempts <= 0) {
          onGroundOnly = true;
        }
      }

      // check every now and then if we have already used up 10ms of time
      if (tries >= 50) {
        tries = 0;
        if (TimeKeeper::getCurrent() - start > 0.01f) {
          if (bestDist < 0.0f) { // haven't found a single spot
            //Just drop the sucka in, and pray
            pos[0] = testPos[0];
            pos[1] = testPos[1];
            pos[2] = maxWorldHeight;
	    DEBUG1("Warning: getSpawnLocation ran out of time, just dropping the sucker in\n");
          }
          break;
        }
      }

      // check if spot is safe enough
      bool dangerous = isImminentlyDangerous();
      if (foundspot && !dangerous) {
	float enemyAngle;
	float dist = enemyProximityCheck(enemyAngle);
	if (dist > bestDist) { // best so far
	  bestDist = dist;
	  pos[0] = testPos[0];
	  pos[1] = testPos[1];
	  pos[2] = testPos[2];
	  azimuth = fmod((enemyAngle + M_PI), 2.0f * M_PI);
	}
	if (bestDist < minProximity) { // not good enough, keep looking
	  foundspot = false;
	  minProximity *= 0.99f; // relax requirements a little
	}
      } else if (dangerous) {
	foundspot = false;
      }
    }
    if (building != NULL)
      delete building;
  }
}

SpawnPosition::~SpawnPosition()
{
}

const bool SpawnPosition::isFacing(const float *enemyPos, const float enemyAzimuth,
				   const float deviation) const
{
  // determine angle from source to dest
  // (X) using only x & y, resultant will be z rotation
  float dx = testPos[0] - enemyPos[0];
  float dy = testPos[1] - enemyPos[1];
  float angActual;
  if (dx == 0) {
    // avoid divide by zero error
    angActual = (float)tan(dy / (1 / 1e12f));
  } else {
    angActual = (float)tan(dy / dx);
  }

  // see if our heading angle is within the bounds set by deviation
  // (X) only compare to z-rotation since that's all we're using
  if (((angActual + deviation / 2) > enemyAzimuth) &&
      ((angActual - deviation / 2) < enemyAzimuth)) {
    return true;
  } else {
    return false;
  }
}

const bool SpawnPosition::isImminentlyDangerous() const
{
  for (int i = 0; i < curMaxPlayers; i++) {
    if (player[i].isAlive() && areFoes(player[i].getTeam(), team)) {
      const FlagInfo *finfo =&flag[player[i].getFlag()];
      const FlagType *ftype = finfo->flag.type;
      float *enemyPos = lastState[i].pos;
      float enemyAngle = lastState[i].azimuth;
      // check for dangerous flags, etc
      // FIXME: any more?
      if (ftype == Flags::Laser) {  // don't spawn in the line of sight of an L
	if (isFacing(enemyPos, enemyAngle, M_PI / 9)) { // he's looking within 20 degrees of spawn point
	  return true;	// eek, don't spawn here
	}
      } else if (ftype == Flags::ShockWave) {  // don't spawn next to a SW
	if (distanceFrom(enemyPos) < safeSWRadius) { // too close to SW
	  return true;	// eek, don't spawn here
	}
      }
      // don't spawn in the line of sight of a normal-shot tank within a certain distance
      if (distanceFrom(enemyPos) < safeDistance) { // within danger zone?
	if (isFacing(enemyPos, enemyAngle, M_PI / 9)) { //and he's looking at me
	  return true;
	}
      }
    }
  }

  // TODO: should check world weapons also

  return false;
}

const float SpawnPosition::enemyProximityCheck(float &enemyAngle) const
{
  float worstDist = 1e12f; // huge number
  bool noEnemy    = true;

  for (int i = 0; i < curMaxPlayers; i++) {
    if (player[i].isAlive() && areFoes(player[i].getTeam(), team)) {
      float *enemyPos = lastState[i].pos;
      if (fabs(enemyPos[2] - testPos[2]) < 1.0f) {
        float x = enemyPos[0] - testPos[0];
        float y = enemyPos[1] - testPos[1];
        float distSq = x * x + y * y;
        if (distSq < worstDist) {
          worstDist  = distSq;
	  enemyAngle = lastState[i].azimuth;
	  noEnemy    = false;
	}
      }
    }
  }
  if (noEnemy)
    enemyAngle = (float)bzfrand() * 2.0f * M_PI;
  return sqrtf(worstDist);
}

const float SpawnPosition::distanceFrom(const float* farPos) const
{
  float dx = farPos[0] - testPos[0];
  float dy = farPos[1] - testPos[1];
  float dz = farPos[2] - testPos[2];
  return (float)sqrt(dx*dx + dy*dy + dz*dz);
}

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


<commit_before>//=============================================================================
// ■ VMDE/global.hpp
//-----------------------------------------------------------------------------
//   在VMDE的内部，包容一切的头。
//   关于只有一个头：
//   我完全无法理解为什么C要采用有头文件的设计。当然这很可能是历史遗留问题，
//   但这导致了C程序员总是需要花费大量时间整理头文件，且这时间花得莫名其妙。
//   而这在其他任何语系中都不是问题：即使是采用C语法的衍生语言，也不会保留
//   这个“特性”。因此我们需要解决它。
//   采用#include管理多文件编译是最好的解决方案。在现今编译单个文件的速度有极
//   大提升的时代，一个个编译小文件却慢得多。然而这个解决方案并不能在像VMDE这
//   样的合作项目中进行，因为它会让很多已建立的条件反射、编译器、IDE日狗。
//   单一头文件也是一个可行的方案。它保留了头文件，但只有一个——这意味着所有
//   你需要编辑的头都在固定的位置，给身体和手带来了放松的空间，同时也保证编辑
//   器的标签栏（如果有的话）不会因为头文件而缩小为原来的一半。
//   当然，还有一个解决办法就是只用一个.c文件。结果：从此这个项目就没落了。
//   综上所述，我选择狗带，砍了其他所有头。
//=============================================================================

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <math.h>
#include <ctime>
#include <thread>
#include <cstdarg>
#include <cassert>

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <SOIL.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <portaudio.h>
#include <vorbis/vorbisfile.h>

#ifndef _INCLUDE_GLOBAL_H
	#define _INCLUDE_GLOBAL_H
	using namespace std;
	//-------------------------------------------------------------------------
	// ● PY Deal For ＭICR0$○F┬ Ｗindoges (ᴚ)
	//Becuase it;s Windoges,I jsut dno't want to use CORERCT ENGRISh &忠闻吔屎炉此
	//-------------------------------------------------------------------------
	#ifdef __MINGW32__
		#define EXPORTED extern "C" __declspec(dllexport)
	#else
		#define EXPORTED extern "C"
	#endif
	#ifdef __CYGWIN__
		#warning This will not work in Cygwin. Try at your own risk.
	#endif
	//-------------------------------------------------------------------------
	// ● 定义宏魔法
	//-------------------------------------------------------------------------
	// NDEBUG - 给assert用的，虽然好像这项目里没人用assert
	// 这个DEBUG在make的时候加（make debug）。
	#ifdef DEBUG
		#define NDEBUG
	#else
		#undef NDEBUG
	#endif
	// ARRAY_SIZE - 获取定义的数组大小
	//     int a[56]; → ARRAY_SIZE(a) == 56
	#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
	// MLVN - Macro-Local Variable Name
	// 这是为了不让明文变量名被莫名其妙使用而设计的。
	//     #define TEST do { int MLVN() = 0, MLVN(another) = 0; } while(0)
	#define MLVN(name) My_Lovely_##name##___
	// NTIMES - 执行多次
	//     NTIMES(3) puts("Three or more, use a for");
	#define NTIMES(n) \
		for (unsigned short MLVN(index) = 0; MLVN(index) < (n); MLVN(index)++)
	// TWICE - 执行两次
	//     TWICE a++;
	//     TWICE { a++; b++; }
	#define TWICE NTIMES(2)
	// XE - 释放内存黑魔法
	// “X掉Exceptions”！
	//     void* m;
	//     m = malloc(1); xe(free, m);
	//     m = (void*) 0; xe(free, m);
	//     m = new float; xe(delete, m);
	//     m = new tm[4]; xe(delete[], m);
	#define XE(method, pointer) do { \
		if (pointer) { \
			method(pointer); \
			(pointer) = NULL; \
		} \
	} while (false)
	// VMDE_Dispose - 一键销毁宏魔法
	#define VMDE_Dispose(method, object) do { \
		if (object) { \
			method(object); \
			(object) = NULL; \
		} \
	} while (false)
	// DEBUG_ENVIRONMENT - 调试环境
	// 这个字符串会显示在log函数输出的开头。
	#define DEBUG_ENVIRONMENT "VMDE"
	// log - Util::log_internal的方便缩写，可以直接得到当前函数名
	//     log("%p", log);
	#define log(...) Util::log_internal(DEBUG_ENVIRONMENT, __func__, __VA_ARGS__)
	// mark - 逐行打log的偷懒大法
	#define mark log("Mark on line %d of %s", __LINE__, __FILE__)
	// error - 抛出错误并终止程序
	#define error(...) do { \
		log(__VA_ARGS__); \
		perror("perror()"); \
		fputs("The errno may not help.\n", stderr); \
		exit(1); \
	} while (false)
	//-------------------------------------------------------------------------
	// ● util.cpp
	//   这个Util的意义已经远超utility。
	//-------------------------------------------------------------------------
	namespace Util {
		extern FILE* log_file;
		void init();
		void terminate();
		void log_internal(const char*, const char*, const char*, ...)
			__attribute__((format(printf, 3, 4)));
		char* read_file(const char* filename);
	}
	//-------------------------------------------------------------------------
	// ● 子文件夹中的头文件
	//-------------------------------------------------------------------------
	#include "VLib/V.hpp"
	#include "Audio/audio.hpp"
	//-------------------------------------------------------------------------
	// ● init.cpp
	//-------------------------------------------------------------------------
	EXPORTED void init_engine(int w, int h, const char* title);
	void setup_viewport();
	void init_vmde(int w, int h);
	//-------------------------------------------------------------------------
	// ● terminate.cpp
	//-------------------------------------------------------------------------
	EXPORTED void terminate_engine();
	//-------------------------------------------------------------------------
	// ● main.cpp
	//-------------------------------------------------------------------------
	void glfw_error_callback(int error, const char* description);
	void main_draw_start();
	void main_draw_end();
	void main_set_brightness(float b);
	extern glm::mat4 projection, view;
	//-------------------------------------------------------------------------
	// ● RenderObject.cpp
	//-------------------------------------------------------------------------
	class Object {
	public:
		virtual ~Object();
	};

	class RenderObject : public Object {
	public:
		virtual void render() = 0;
	};
	//-------------------------------------------------------------------------
	// ● resources.cpp
	//-------------------------------------------------------------------------
	namespace Res {
		class Texture : public Object {
		public:
			GLuint texture, index;
			int width, height;
			Texture(const char* file);
			Texture(const char* file, bool useLinear);

			~Texture();
		};
	}
	//-------------------------------------------------------------------------
	// ● shaders.cpp
	//-------------------------------------------------------------------------
	class Shaders : public Object {
	public:
		GLuint basic_2D_vsh;
		GLuint basic_2D_fsh;
		GLuint shaderProgram;

		GLuint UBO_matrix;
		glm::mat4* mat = new glm::mat4[3];

	public:
		Shaders(const GLchar* vsh_src_ptr, const GLchar* fsh_src_ptr);
		static Shaders* CreateFromFile(const char* vsh_src, const char* fsh_src);

		void link_program();
		void use();

		void set_float(const char* identifier, GLfloat value);
		void set_int(const char* identifier, GLint value);
		void set_texture(const char* identifier, Res::Texture* tex, GLuint index);

		void ProjectionView(glm::mat4 projection, glm::mat4 view);

		~Shaders();
	};
	extern Shaders* _shaders_in_use;
	//-------------------------------------------------------------------------
	// ● VMDE
	//-------------------------------------------------------------------------
	struct VMDEState {
		bool frozen;
		float brightness;
	};

	enum GL_VER {
		GL_33,
		GL_43,
		VULKAN // Not used
	};

	struct VMDE {
		VMDEState state;
		long frame_count;
		long millisecond;
		int width, height;
		int fps;
		double frame_time;
		bool done;

		int gl_ver;
	};

	// VMDE操控的全局变量
	extern struct VMDE* VMDE;
	extern GLFWwindow* window;

	//-------------------------------------------------------------------------
	// ● GDrawable
	//-------------------------------------------------------------------------
	struct Vertex {
		glm::vec3 world_position;
		glm::vec4 color;
		glm::vec2 st;
		glm::vec3 normal;
	};

	class GDrawable : public RenderObject {
	public:
		struct Data {
			Vertex* vertices;
			int vtx_c;
			GLuint* indices;
			int ind_c;
			GLuint* mat;
			int mat_c;
			GLuint MBO;
			GLuint VAO;
			GLuint VBO;
			GLuint EBO;
			glm::mat4 model;
		} data;

		void render(GLuint start, GLuint end);
		void render();
		void fbind();
		void update();
		void update_instance();
		void update_instance_alien_size();

		~GDrawable();

		GDrawable();
	};

	//-------------------------------------------------------------------------
	// ● Text
	//-------------------------------------------------------------------------
	class TextRenderer : public RenderObject {
	private:
		GDrawable* obj;
		Res::Texture* tex;
		Shaders* texshader;

	public:
		enum TextDecorationType {
			NONE,
			SHADOW,
			OUTLINE,
		};
		TextRenderer();
		void BakeText(
			const char* text,
			float width, float height,
			TextDecorationType decoration
		);
		void render();
		void instanceRenderText(
				const char* text,
				glm::mat4 projection, glm::mat4 view, glm::mat4 transform,
				float width, float height, TextDecorationType decoration
		);
		~TextRenderer();
	};

#endif
<commit_msg>添加errorp<commit_after>//=============================================================================
// ■ VMDE/global.hpp
//-----------------------------------------------------------------------------
//   在VMDE的内部，包容一切的头。
//   关于只有一个头：
//   我完全无法理解为什么C要采用有头文件的设计。当然这很可能是历史遗留问题，
//   但这导致了C程序员总是需要花费大量时间整理头文件，且这时间花得莫名其妙。
//   而这在其他任何语系中都不是问题：即使是采用C语法的衍生语言，也不会保留
//   这个“特性”。因此我们需要解决它。
//   采用#include管理多文件编译是最好的解决方案。在现今编译单个文件的速度有极
//   大提升的时代，一个个编译小文件却慢得多。然而这个解决方案并不能在像VMDE这
//   样的合作项目中进行，因为它会让很多已建立的条件反射、编译器、IDE日狗。
//   单一头文件也是一个可行的方案。它保留了头文件，但只有一个——这意味着所有
//   你需要编辑的头都在固定的位置，给身体和手带来了放松的空间，同时也保证编辑
//   器的标签栏（如果有的话）不会因为头文件而缩小为原来的一半。
//   当然，还有一个解决办法就是只用一个.c文件。结果：从此这个项目就没落了。
//   综上所述，我选择狗带，砍了其他所有头。
//=============================================================================

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <math.h>
#include <ctime>
#include <thread>
#include <cstdarg>
#include <cassert>

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <SOIL.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <portaudio.h>
#include <vorbis/vorbisfile.h>

#ifndef _INCLUDE_GLOBAL_H
	#define _INCLUDE_GLOBAL_H
	using namespace std;
	//-------------------------------------------------------------------------
	// ● PY Deal For ＭICR0$○F┬ Ｗindoges (ᴚ)
	//Becuase it;s Windoges,I jsut dno't want to use CORERCT ENGRISh &忠闻吔屎炉此
	//-------------------------------------------------------------------------
	#ifdef __MINGW32__
		#define EXPORTED extern "C" __declspec(dllexport)
	#else
		#define EXPORTED extern "C"
	#endif
	#ifdef __CYGWIN__
		#warning This will not work in Cygwin. Try at your own risk.
	#endif
	//-------------------------------------------------------------------------
	// ● 定义宏魔法
	//-------------------------------------------------------------------------
	// NDEBUG - 给assert用的，虽然好像这项目里没人用assert
	// 这个DEBUG在make的时候加（make debug）。
	#ifdef DEBUG
		#define NDEBUG
	#else
		#undef NDEBUG
	#endif
	// ARRAY_SIZE - 获取定义的数组大小
	//     int a[56]; → ARRAY_SIZE(a) == 56
	#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
	// MLVN - Macro-Local Variable Name
	// 这是为了不让明文变量名被莫名其妙使用而设计的。
	//     #define TEST do { int MLVN() = 0, MLVN(another) = 0; } while(0)
	#define MLVN(name) My_Lovely_##name##___
	// NTIMES - 执行多次
	//     NTIMES(3) puts("Three or more, use a for");
	#define NTIMES(n) \
		for (unsigned short MLVN(index) = 0; MLVN(index) < (n); MLVN(index)++)
	// TWICE - 执行两次
	//     TWICE a++;
	//     TWICE { a++; b++; }
	#define TWICE NTIMES(2)
	// XE - 释放内存黑魔法
	// “X掉Exceptions”！
	//     void* m;
	//     m = malloc(1); xe(free, m);
	//     m = (void*) 0; xe(free, m);
	//     m = new float; xe(delete, m);
	//     m = new tm[4]; xe(delete[], m);
	#define XE(method, pointer) do { \
		if (pointer) { \
			method(pointer); \
			(pointer) = NULL; \
		} \
	} while (false)
	// VMDE_Dispose - 一键销毁宏魔法
	#define VMDE_Dispose(method, object) do { \
		if (object) { \
			method(object); \
			(object) = NULL; \
		} \
	} while (false)
	// DEBUG_ENVIRONMENT - 调试环境
	// 这个字符串会显示在log函数输出的开头。
	#define DEBUG_ENVIRONMENT "VMDE"
	// log - Util::log_internal的方便缩写，可以直接得到当前函数名
	//     log("%p", log);
	#define log(...) Util::log_internal(DEBUG_ENVIRONMENT, __func__, __VA_ARGS__)
	// mark - 逐行打log的偷懒大法
	#define mark log("Mark on line %d of %s", __LINE__, __FILE__)
	// error_internal
	#define error_internal(extra, ...) do { \
		log(__VA_ARGS__); \
		extra \
		exit(1); \
	} while (false)
	// error - 抛出错误并终止程序
	#define error(...) error_internal(, __VA_ARGS__)
	// errorp - error + perror
	#define errorp(...) error_internal(perror("perror()"), __VA_ARGS__)
	//-------------------------------------------------------------------------
	// ● util.cpp
	//   这个Util的意义已经远超utility。
	//-------------------------------------------------------------------------
	namespace Util {
		extern FILE* log_file;
		void init();
		void terminate();
		void log_internal(const char*, const char*, const char*, ...)
			__attribute__((format(printf, 3, 4)));
		char* read_file(const char* filename);
	}
	//-------------------------------------------------------------------------
	// ● 子文件夹中的头文件
	//-------------------------------------------------------------------------
	#include "VLib/V.hpp"
	#include "Audio/audio.hpp"
	//-------------------------------------------------------------------------
	// ● init.cpp
	//-------------------------------------------------------------------------
	EXPORTED void init_engine(int w, int h, const char* title);
	void setup_viewport();
	void init_vmde(int w, int h);
	//-------------------------------------------------------------------------
	// ● terminate.cpp
	//-------------------------------------------------------------------------
	EXPORTED void terminate_engine();
	//-------------------------------------------------------------------------
	// ● main.cpp
	//-------------------------------------------------------------------------
	void glfw_error_callback(int error, const char* description);
	void main_draw_start();
	void main_draw_end();
	void main_set_brightness(float b);
	extern glm::mat4 projection, view;
	//-------------------------------------------------------------------------
	// ● RenderObject.cpp
	//-------------------------------------------------------------------------
	class Object {
	public:
		virtual ~Object();
	};

	class RenderObject : public Object {
	public:
		virtual void render() = 0;
	};
	//-------------------------------------------------------------------------
	// ● resources.cpp
	//-------------------------------------------------------------------------
	namespace Res {
		class Texture : public Object {
		public:
			GLuint texture, index;
			int width, height;
			Texture(const char* file);
			Texture(const char* file, bool useLinear);

			~Texture();
		};
	}
	//-------------------------------------------------------------------------
	// ● shaders.cpp
	//-------------------------------------------------------------------------
	class Shaders : public Object {
	public:
		GLuint basic_2D_vsh;
		GLuint basic_2D_fsh;
		GLuint shaderProgram;

		GLuint UBO_matrix;
		glm::mat4* mat = new glm::mat4[3];

	public:
		Shaders(const GLchar* vsh_src_ptr, const GLchar* fsh_src_ptr);
		static Shaders* CreateFromFile(const char* vsh_src, const char* fsh_src);

		void link_program();
		void use();

		void set_float(const char* identifier, GLfloat value);
		void set_int(const char* identifier, GLint value);
		void set_texture(const char* identifier, Res::Texture* tex, GLuint index);

		void ProjectionView(glm::mat4 projection, glm::mat4 view);

		~Shaders();
	};
	extern Shaders* _shaders_in_use;
	//-------------------------------------------------------------------------
	// ● VMDE
	//-------------------------------------------------------------------------
	struct VMDEState {
		bool frozen;
		float brightness;
	};

	enum GL_VER {
		GL_33,
		GL_43,
		VULKAN // Not used
	};

	struct VMDE {
		VMDEState state;
		long frame_count;
		long millisecond;
		int width, height;
		int fps;
		double frame_time;
		bool done;

		int gl_ver;
	};

	// VMDE操控的全局变量
	extern struct VMDE* VMDE;
	extern GLFWwindow* window;

	//-------------------------------------------------------------------------
	// ● GDrawable
	//-------------------------------------------------------------------------
	struct Vertex {
		glm::vec3 world_position;
		glm::vec4 color;
		glm::vec2 st;
		glm::vec3 normal;
	};

	class GDrawable : public RenderObject {
	public:
		struct Data {
			Vertex* vertices;
			int vtx_c;
			GLuint* indices;
			int ind_c;
			GLuint* mat;
			int mat_c;
			GLuint MBO;
			GLuint VAO;
			GLuint VBO;
			GLuint EBO;
			glm::mat4 model;
		} data;

		void render(GLuint start, GLuint end);
		void render();
		void fbind();
		void update();
		void update_instance();
		void update_instance_alien_size();

		~GDrawable();

		GDrawable();
	};

	//-------------------------------------------------------------------------
	// ● Text
	//-------------------------------------------------------------------------
	class TextRenderer : public RenderObject {
	private:
		GDrawable* obj;
		Res::Texture* tex;
		Shaders* texshader;

	public:
		enum TextDecorationType {
			NONE,
			SHADOW,
			OUTLINE,
		};
		TextRenderer();
		void BakeText(
			const char* text,
			float width, float height,
			TextDecorationType decoration
		);
		void render();
		void instanceRenderText(
				const char* text,
				glm::mat4 projection, glm::mat4 view, glm::mat4 transform,
				float width, float height, TextDecorationType decoration
		);
		~TextRenderer();
	};

#endif
<|endoftext|>


<commit_before>#include "VTThread.h"
#include "VTThread_p.h"

#include "VTCriticalSection.h"

#include <stdexcept>
#include <string>

#include <assert.h>
#include <Windows.h>

std::string get_win_error_msg()
{
    std::string msg;
    char* errorText = NULL;

    FormatMessage(
        // use system message tables to retrieve error text
        FORMAT_MESSAGE_FROM_SYSTEM
        // allocate buffer on local heap for error text
        | FORMAT_MESSAGE_ALLOCATE_BUFFER
        // Important! will fail otherwise, since we're not
        // (and CANNOT) pass insertion parameters
        | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,                      // unused with FORMAT_MESSAGE_FROM_SYSTEM
        GetLastError(),
        MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
        ( LPTSTR )&errorText,      // output
        0,                         // minimum size for output buffer
        NULL );

    if ( NULL != errorText )
    {
        msg = errorText;

        // release memory allocated by FormatMessage()
        LocalFree( errorText );
        errorText = NULL;
    }

    return msg;
}

struct VT::Thread::Impl
{
    Impl()
        : thread_handle(NULL)
        , state(detail_::ThreadState::NotStarted)
        , state_lock()
    { }

    static unsigned long WINAPI thread_start(void* params);

    HANDLE  thread_handle;
    int     state;
    mutable VT::CriticalSection state_lock;
};

unsigned long WINAPI VT::Thread::Impl::thread_start( void* params )
{
    Thread* t = reinterpret_cast<Thread*>( params );
    {
        VT::CSLocker lock(t->pimpl_->state_lock);
        t->pimpl_->state = detail_::ThreadState::Running;
    }
    t->run();
    {
        VT::CSLocker lock(t->pimpl_->state_lock);
        t->pimpl_->state = detail_::ThreadState::Finished;
    }
    return 0;
}

void VT::Thread::sleep(int time_ms)
{
    Sleep(time_ms);
}

unsigned long VT::Thread::current_thread_id()
{
    return GetCurrentThreadId();
}

VT::Thread::Thread()
    : pimpl_(new Impl)
{
}

VT::Thread::~Thread()
{
    if ( pimpl_->thread_handle != NULL )
        CloseHandle( pimpl_->thread_handle );
}

void VT::Thread::start()
{
    {
        VT::CSLocker lock(pimpl_->state_lock);
        if (pimpl_->state != detail_::ThreadState::NotStarted)
            throw std::runtime_error("Thread already started");
        pimpl_->state = detail_::ThreadState::Init;
    }

    HANDLE h = CreateThread( NULL, 0, &Impl::thread_start, this, 0, NULL );

    if ( h == NULL )
    {
        throw std::runtime_error( "Failed to create thread. Error: " + get_win_error_msg() );
    }

    pimpl_->thread_handle = h;
}

bool VT::Thread::join( int timeout_millis )
{
    assert( timeout_millis >= -1 );

    {
        VT::CSLocker lock(pimpl_->state_lock);
        if ( pimpl_->state == detail_::ThreadState::NotStarted || pimpl_->state == detail_::ThreadState::Finished )
            return true;
    }

    int result = WaitForSingleObject( pimpl_->thread_handle, ( timeout_millis == -1 ? INFINITE : timeout_millis ) );

    if ( result == WAIT_TIMEOUT )
    {
        return false;
    }
    else if ( result == WAIT_FAILED )
    {
        throw std::runtime_error( "Something went terribly wrong inside MyThread class. Failed to wait on running thread." );
    }
    else
    {
        return true;
    }
}

bool VT::Thread::isRunning() const
{
    VT::CSLocker lock(pimpl_->state_lock);
    return ( pimpl_->state == detail_::ThreadState::Running || pimpl_->state == detail_::ThreadState::Init );
}

bool VT::Thread::isFinished() const
{
    VT::CSLocker lock(pimpl_->state_lock);
    return ( pimpl_->state == detail_::ThreadState::Finished );
}

unsigned long VT::Thread::id() const
{
    return GetThreadId(pimpl_->thread_handle);
}
<commit_msg>VT::Thread Win assertion fix<commit_after>#include "VTThread.h"
#include "VTThread_p.h"

#include "VTCriticalSection.h"

#include <stdexcept>
#include <string>

#include <assert.h>
#include <Windows.h>

std::string get_win_error_msg()
{
    std::string msg;
    char* errorText = NULL;

    FormatMessage(
        // use system message tables to retrieve error text
        FORMAT_MESSAGE_FROM_SYSTEM
        // allocate buffer on local heap for error text
        | FORMAT_MESSAGE_ALLOCATE_BUFFER
        // Important! will fail otherwise, since we're not
        // (and CANNOT) pass insertion parameters
        | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,                      // unused with FORMAT_MESSAGE_FROM_SYSTEM
        GetLastError(),
        MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
        ( LPTSTR )&errorText,      // output
        0,                         // minimum size for output buffer
        NULL );

    if ( NULL != errorText )
    {
        msg = errorText;

        // release memory allocated by FormatMessage()
        LocalFree( errorText );
        errorText = NULL;
    }

    return msg;
}

struct VT::Thread::Impl
{
    Impl()
        : thread_handle(NULL)
        , state(detail_::ThreadState::NotStarted)
        , state_lock()
    { }

    static unsigned long WINAPI thread_start(void* params);

    HANDLE  thread_handle;
    int     state;
    mutable VT::CriticalSection state_lock;
};

unsigned long WINAPI VT::Thread::Impl::thread_start( void* params )
{
    Thread* t = reinterpret_cast<Thread*>( params );
    {
        VT::CSLocker lock(t->pimpl_->state_lock);
        t->pimpl_->state = detail_::ThreadState::Running;
    }
    t->run();
    {
        VT::CSLocker lock(t->pimpl_->state_lock);
        t->pimpl_->state = detail_::ThreadState::Finished;
    }
    return 0;
}

void VT::Thread::sleep(int time_ms)
{
    Sleep(time_ms);
}

unsigned long VT::Thread::current_thread_id()
{
    return GetCurrentThreadId();
}

VT::Thread::Thread()
    : pimpl_(new Impl)
{
}

VT::Thread::~Thread()
{
    if ( pimpl_->thread_handle != NULL )
        CloseHandle( pimpl_->thread_handle );
}

void VT::Thread::start()
{
    {
        VT::CSLocker lock(pimpl_->state_lock);
        assert(pimpl_->state == detail_::ThreadState::NotStarted && "Attempt to start VT::Thread twice");
        pimpl_->state = detail_::ThreadState::Init;
    }

    HANDLE h = CreateThread( NULL, 0, &Impl::thread_start, this, 0, NULL );

    if ( h == NULL )
    {
        throw std::runtime_error( "Failed to create thread. Error: " + get_win_error_msg() );
    }

    pimpl_->thread_handle = h;
}

bool VT::Thread::join( int timeout_millis )
{
    assert( timeout_millis >= -1 );
    assert( pimpl_->state != detail_::ThreadState::NotStarted && "Attempt to join not started VT::Thread");

    {
        VT::CSLocker lock(pimpl_->state_lock);
        if ( pimpl_->state == detail_::ThreadState::NotStarted || pimpl_->state == detail_::ThreadState::Finished )
            return true;
    }

    int result = WaitForSingleObject( pimpl_->thread_handle, ( timeout_millis == -1 ? INFINITE : timeout_millis ) );

    if ( result == WAIT_TIMEOUT )
    {
        return false;
    }
    else if ( result == WAIT_FAILED )
    {
        throw std::runtime_error( "Something went terribly wrong inside MyThread class. Failed to wait on running thread." );
    }
    else
    {
        return true;
    }
}

bool VT::Thread::isRunning() const
{
    VT::CSLocker lock(pimpl_->state_lock);
    return ( pimpl_->state == detail_::ThreadState::Running || pimpl_->state == detail_::ThreadState::Init );
}

bool VT::Thread::isFinished() const
{
    VT::CSLocker lock(pimpl_->state_lock);
    return ( pimpl_->state == detail_::ThreadState::Finished );
}

unsigned long VT::Thread::id() const
{
    return GetThreadId(pimpl_->thread_handle);
}
<|endoftext|>


<commit_before>#include "stdafx.h"
#include "WindowEvent.h"
using namespace coma2d;

const std::string WindowEvent::CREATED			= "created";
const std::string WindowEvent::DESTROY			= "destroy";

const std::string WindowEvent::ACTIVATED		= "activated";
const std::string WindowEvent::INACTIVATED		= "inactivated";

const std::string WindowEvent::MINIMIZED		= "minimized";
const std::string WindowEvent::MAXIMIZED		= "maximized";
const std::string WindowEvent::RESTORED			= "restored";
const std::string WindowEvent::ENTER_FULLSCREEN	= "enterFullscreen";
const std::string WindowEvent::EXIT_FULLSCREEN	= "exitFullscreen";

const std::string WindowEvent::ENTER_RESIZEMOVE	= "enterResizeMove";
const std::string WindowEvent::EXIT_RESIZEMOVE	= "exitResizeMove";
const std::string WindowEvent::RESIZE			= "resize";
const std::string WindowEvent::RESIZING			= "resizing";
const std::string WindowEvent::MOVE				= "move";
const std::string WindowEvent::MOVING			= "moving";

const std::string WindowEvent::UPDATE			= "update";

WindowEvent::WindowEvent(std::string type, ComaWindow* target, WPARAM wParam, LPARAM lParam)
	:Event(type)
{
	this->target = target;
	this->wParam = wParam;
	this->lParam = lParam;
}

WindowEvent::~WindowEvent()
{
}
<commit_msg>윈도우 이벤트 형식 수정<commit_after>#include "stdafx.h"
#include "WindowEvent.h"
using namespace coma2d;

const std::string WindowEvent::CREATED			= "window_created";
const std::string WindowEvent::DESTROY			= "window_destroy";

const std::string WindowEvent::ACTIVATED		= "window_activated";
const std::string WindowEvent::INACTIVATED		= "window_inactivated";

const std::string WindowEvent::MINIMIZED		= "window_minimized";
const std::string WindowEvent::MAXIMIZED		= "window_maximized";
const std::string WindowEvent::RESTORED			= "window_restored";
const std::string WindowEvent::ENTER_FULLSCREEN	= "window_enterFullscreen";
const std::string WindowEvent::EXIT_FULLSCREEN	= "window_exitFullscreen";

const std::string WindowEvent::ENTER_RESIZEMOVE	= "window_enterResizeMove";
const std::string WindowEvent::EXIT_RESIZEMOVE	= "window_exitResizeMove";
const std::string WindowEvent::RESIZE			= "window_resize";
const std::string WindowEvent::RESIZING			= "window_resizing";
const std::string WindowEvent::MOVE				= "window_move";
const std::string WindowEvent::MOVING			= "window_moving";

const std::string WindowEvent::UPDATE			= "window_update";

WindowEvent::WindowEvent(std::string type, ComaWindow* target, WPARAM wParam, LPARAM lParam)
	:Event(type)
{
	this->target = target;
	this->wParam = wParam;
	this->lParam = lParam;
}

WindowEvent::~WindowEvent()
{
}
<|endoftext|>